Appearance
Authentication Integration
All frontend apps authenticate via Keycloak OIDC. The microtec-auth-lib library encapsulates every authentication concern so that remote apps never deal with Keycloak directly. This page covers how auth is initialised, how tokens flow through HTTP requests, and how guards protect routes.
Overview
Keycloak Realms
| App | Realm | Why |
|---|---|---|
erp-home and all ERP remotes | microtec | Shared ERP identity space for all tenant users |
bussiness-owners | businessowner | Separate identity space for Business Owners |
Realm selection is automatic — KeycloakInitFactory reads the current URL path at app bootstrap:
typescript
const realm = window.location.pathname.startsWith('/bussiness-owners')
? 'businessowner'
: config.realm; // 'microtec'There is no manual realm configuration in remote apps. The environment file carries keycloakRealm: 'microtec' and the factory uses it unless the BO path is detected.
microtec-auth-lib — Public API
AuthService
The central authentication service. Inject it in any component or service that needs to check identity or control session lifecycle.
typescript
import { AuthService } from 'microtec-auth-lib';
@Component({ ... })
export class MyComponent {
constructor(private auth: AuthService) {}
get username(): string {
return this.auth.getUserClaims().preferred_username;
}
get tenantId(): string {
return this.auth.getUserClaims().tenant_id;
}
hasPermission(role: string): boolean {
return this.auth.hasRole(role);
}
logout(): void {
this.auth.logout();
}
}| Method | Returns | Description |
|---|---|---|
isAuthenticated() | boolean | True if a valid access token is held |
getUserClaims() | KeycloakTokenParsed | Decoded JWT claims (username, email, roles, tenant_id) |
hasRole(role) | boolean | True if the user holds the given Keycloak role |
getToken() | Promise<string> | Returns a fresh access token, triggering refresh if needed |
login() | void | Redirects to Keycloak login page |
logout() | void | Clears session and redirects to Keycloak logout |
AuthGuard
Protects routes from unauthenticated access. Returns true if authenticated; otherwise redirects to Keycloak login.
typescript
// apps/apps-accounting/src/app/accounting.routes.ts
import { AuthGuard } from 'microtec-auth-lib';
export const ACCOUNTING_ROUTES = [
{
path: '',
canActivate: [AuthGuard],
children: [
{ path: 'invoices', component: InvoiceListComponent },
{ path: 'journal-entries', component: JournalEntryListComponent },
],
},
];PermissionGuard
Checks that the authenticated user holds a specific Keycloak role. Navigates to a 403 "access denied" page if the role is missing.
typescript
import { PermissionGuard } from 'microtec-auth-lib';
export const ACCOUNTING_ROUTES = [
{
path: 'post-invoice',
canActivate: [AuthGuard, PermissionGuard],
data: { requiredRole: 'accounting.invoice.post' },
component: PostInvoiceComponent,
},
];The role name convention is <module>.<resource>.<action>, e.g. accounting.invoice.post, hr.employee.edit, inventory.item.delete.
KeycloakInterceptor
Automatically injects the Bearer token into every outbound HTTP request. Remote apps do not need to add Authorization headers manually.
typescript
// Registration in app.module.ts (already done in MicrotecAuthModule.forRoot())
providers: [
{
provide: HTTP_INTERCEPTORS,
useClass: KeycloakInterceptor,
multi: true,
},
],The interceptor calls authService.getToken() before each request, which transparently refreshes the token if it is expiring within 5 minutes.
Module Registration
Register MicrotecAuthModule in the root AppModule of each remote app:
typescript
// apps/apps-accounting/src/app/app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { HttpClientModule } from '@angular/common/http';
import { MicrotecAuthModule } from 'microtec-auth-lib';
import { environment } from '../environments/environment';
@NgModule({
imports: [
BrowserModule,
HttpClientModule,
MicrotecAuthModule.forRoot({
keycloakUrl: environment.keycloakUrl,
realm: environment.keycloakRealm,
clientId: 'angular',
}),
],
bootstrap: [AppComponent],
})
export class AppModule {}MicrotecAuthModule.forRoot() registers the APP_INITIALIZER that calls KeycloakInitFactory, the KeycloakInterceptor, and provides AuthService and both guards.
Token Refresh
Keycloak tokens have a short lifetime (typically 5 minutes for access tokens). Refresh is handled automatically:
keycloak-jsruns a background timer and refreshes the token 60 seconds before it expires.KeycloakInterceptoralso callsgetToken()before each request, which triggers a refresh if the token expires within 5 minutes.- Refresh tokens have a longer lifetime (configured in the Keycloak realm — typically 30 minutes).
- If the refresh token also expires, the user is redirected to the Keycloak login page.
No polling required
The refresh mechanism is event-driven, not polled. You do not need setInterval or manual token management in any remote app.
Cross-App SSO
When a user is logged into erp-home (port 4401 locally) and navigates to apps-accounting (port 4402 locally), they do not need to log in again. Keycloak's server-side session handles SSO:
- The accounting app runs
keycloak.init({ onLoad: 'check-sso' }). - Keycloak checks the existing session via a hidden iframe (silent check SSO).
- If a session exists, tokens are issued without a login prompt.
- The
silent-check-sso.htmlfile inshared-assetshandles the iframe callback.
Silent SSO file must be present
Every app's deployed output must contain assets/silent-check-sso.html. This file is distributed by publishAssets automatically. If it is missing, SSO fails and users see repeated login prompts when switching apps.
Environment File Auth Config
Each environment file carries the Keycloak connection details:
typescript
export const environment = {
keycloakUrl: 'https://keycloak.onlinemicrotec.com.sa',
keycloakRealm: 'microtec',
keycloakClientId: 'angular',
// ... other config
};| Environment | Keycloak URL |
|---|---|
| dev / local | https://keycloak.microtec-test.com |
| stage | https://keycloak.microtecstage.com |
| UAT | https://keycloak.microtec-uat.com |
| production | https://keycloak.onlinemicrotec.com.sa |
The Keycloak realm for the businessowner app is businessowner in all environments. All ERP apps use microtec in all environments.
Keycloak Client Configuration (per app)
Each Angular app has a corresponding Keycloak client in the relevant realm. The client must have:
| Setting | Value |
|---|---|
| Client protocol | openid-connect |
| Access type | public (no client secret) |
| Standard flow enabled | Yes |
| Valid redirect URIs | http://localhost:{port}/*, https://<swa-domain>/* |
| Web origins | http://localhost:{port}, https://<swa-domain> |
| PKCE challenge method | S256 |
Do not use * for Web Origins in production
Wildcard Web Origins bypass CORS protections. Production Keycloak clients must list exact SWA origins.
Troubleshooting
Login loop — user is redirected to Keycloak repeatedly
- Check that
keycloakUrlandkeycloakRealmin the environment file are correct. - Verify the Keycloak client has the app's current URL in its Valid Redirect URIs.
- Ensure
silent-check-sso.htmlexists in the app'sassets/folder. - Check the browser console for Keycloak init errors.
401 errors despite being logged in
- The access token may have expired and the refresh failed. Clear local storage / cookies and log in again.
- Verify
KeycloakInterceptoris registered (checkapp.module.tsimportsMicrotecAuthModule.forRoot()). - Confirm the backend validates the
microtecrealm — abusinessownertoken sent to an ERP endpoint returns 401.
PermissionGuard blocks access unexpectedly
- Check the
data.requiredRolevalue in the route definition. - Verify the role is assigned to the user in Keycloak admin console under the correct realm and client.
- Use
authService.getUserClaims().realm_access.rolesin the browser console to inspect the user's current roles.