Skip to content

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

AppRealmWhy
erp-home and all ERP remotesmicrotecShared ERP identity space for all tenant users
bussiness-ownersbusinessownerSeparate 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();
  }
}
MethodReturnsDescription
isAuthenticated()booleanTrue if a valid access token is held
getUserClaims()KeycloakTokenParsedDecoded JWT claims (username, email, roles, tenant_id)
hasRole(role)booleanTrue if the user holds the given Keycloak role
getToken()Promise<string>Returns a fresh access token, triggering refresh if needed
login()voidRedirects to Keycloak login page
logout()voidClears 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-js runs a background timer and refreshes the token 60 seconds before it expires.
  • KeycloakInterceptor also calls getToken() 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:

  1. The accounting app runs keycloak.init({ onLoad: 'check-sso' }).
  2. Keycloak checks the existing session via a hidden iframe (silent check SSO).
  3. If a session exists, tokens are issued without a login prompt.
  4. The silent-check-sso.html file in shared-assets handles 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
};
EnvironmentKeycloak URL
dev / localhttps://keycloak.microtec-test.com
stagehttps://keycloak.microtecstage.com
UAThttps://keycloak.microtec-uat.com
productionhttps://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:

SettingValue
Client protocolopenid-connect
Access typepublic (no client secret)
Standard flow enabledYes
Valid redirect URIshttp://localhost:{port}/*, https://<swa-domain>/*
Web originshttp://localhost:{port}, https://<swa-domain>
PKCE challenge methodS256

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

  1. Check that keycloakUrl and keycloakRealm in the environment file are correct.
  2. Verify the Keycloak client has the app's current URL in its Valid Redirect URIs.
  3. Ensure silent-check-sso.html exists in the app's assets/ folder.
  4. Check the browser console for Keycloak init errors.

401 errors despite being logged in

  1. The access token may have expired and the refresh failed. Clear local storage / cookies and log in again.
  2. Verify KeycloakInterceptor is registered (check app.module.ts imports MicrotecAuthModule.forRoot()).
  3. Confirm the backend validates the microtec realm — a businessowner token sent to an ERP endpoint returns 401.

PermissionGuard blocks access unexpectedly

  1. Check the data.requiredRole value in the route definition.
  2. Verify the role is assigned to the user in Keycloak admin console under the correct realm and client.
  3. Use authService.getUserClaims().realm_access.roles in the browser console to inspect the user's current roles.

Internal Documentation — Microtec Platform Team