@meshmakers/shared-auth
v3.5.420
Published
OAuth2/OIDC authentication library for Angular applications using [angular-oauth2-oidc](https://github.com/manfredsteyer/angular-oauth2-oidc).
Readme
@meshmakers/shared-auth
OAuth2/OIDC authentication library for Angular applications using angular-oauth2-oidc.
Installation
npm install @meshmakers/shared-authQuick Start
// app.config.ts
import { ApplicationConfig } from '@angular/core';
import { provideHttpClient, withInterceptors } from '@angular/common/http';
import { provideMmSharedAuth, authorizeInterceptor } from '@meshmakers/shared-auth';
export const appConfig: ApplicationConfig = {
providers: [
provideHttpClient(withInterceptors([authorizeInterceptor])),
provideMmSharedAuth(),
]
};// app.component.ts
import { Component, inject, OnInit } from '@angular/core';
import { AuthorizeService } from '@meshmakers/shared-auth';
@Component({...})
export class AppComponent implements OnInit {
private readonly authorizeService = inject(AuthorizeService);
async ngOnInit() {
await this.authorizeService.initialize({
issuer: 'https://your-identity-provider.com/',
redirectUri: window.location.origin,
clientId: 'your-client-id',
scope: 'openid profile email',
wellKnownServiceUris: ['https://api.your-service.com']
});
}
}API Reference
AuthorizeService
The main service for authentication.
Signals (Recommended)
| Signal | Type | Description |
|--------|------|-------------|
| isAuthenticated | Signal<boolean> | Current authentication status |
| user | Signal<IUser \| null> | Current user information |
| accessToken | Signal<string \| null> | OAuth access token |
| userInitials | Signal<string \| null> | User initials (e.g., "JD") |
| issuer | Signal<string \| null> | OAuth issuer URL |
| sessionLoading | Signal<boolean> | Session loading state |
| roles | Signal<string[]> | User roles (computed) |
// Usage in template
@if (authorizeService.isAuthenticated()) {
<p>Welcome, {{ authorizeService.user()?.name }}</p>
}
// Usage in TypeScript
const isAuth = this.authorizeService.isAuthenticated();
const roles = this.authorizeService.roles();Methods
| Method | Description |
|--------|-------------|
| initialize(options) | Initialize OAuth with configuration |
| uninitialize() | Clean up OAuth session |
| login() | Start OAuth login flow |
| logout() | Log out user |
| isInRole(role) | Check if user has specific role |
| getAccessTokenSync() | Get current access token synchronously |
| getServiceUris() | Get configured service URIs |
Guards
Functional guards for route protection.
import { authorizeGuard, authorizeMatchGuard } from '@meshmakers/shared-auth';
const routes: Routes = [
// Basic authentication check
{
path: 'dashboard',
component: DashboardComponent,
canActivate: [authorizeGuard]
},
// With role requirement
{
path: 'admin',
component: AdminComponent,
canActivate: [authorizeGuard],
data: { roles: ['AdminPanelManagement'] }
},
// Lazy-loaded module
{
path: 'reports',
loadChildren: () => import('./reports/routes'),
canMatch: [authorizeMatchGuard]
}
];| Guard | Type | Description |
|-------|------|-------------|
| authorizeGuard | CanActivateFn | Route activation with optional role check |
| authorizeChildGuard | CanActivateFn | Child route guard |
| authorizeMatchGuard | CanMatchFn | Lazy-load matching guard |
| authorizeDeactivateGuard | Function | Always allows deactivation |
Interceptor
Automatically adds Bearer token to HTTP requests.
import { provideHttpClient, withInterceptors } from '@angular/common/http';
import { authorizeInterceptor } from '@meshmakers/shared-auth';
providers: [
provideHttpClient(withInterceptors([authorizeInterceptor]))
]The interceptor adds the Authorization: Bearer <token> header to:
- Same-origin requests (relative URLs)
- Requests to configured
wellKnownServiceUris
It also recovers from an expired access token: when a request that carried a token
comes back 401, the token is refreshed and the request is retried once with the new
bearer. The refresh is single-flight (concurrent 401s share one refresh), and a 401
that arrives after that refresh already finished reuses its result instead of starting
another one. A retry is never itself retried. Requests without a token, the service health
report (/health, which needs the bearer since AB#6739 but is never refreshed on 401) and the token
endpoint itself are left alone, and if the refresh fails the original 401 reaches the
caller.
TenantTopologyService
await inject(TenantTopologyService).load(rootTenantId) returns which of the user's tenants lie below the app root
(topologyLeaves, topologyDirectChildren, topologyTenantIds) or a topologyKnown: false result with a reason; it
never throws. Map every unknown result to "token tenant only". See
docs/README.md.
Error classification
authorizationRefusal(error) tells a 401 and a 403 apart on an error typed unknown,
so each host can phrase the outcome in its own words without writing that status check again.
import { authorizationRefusal } from '@meshmakers/shared-auth';
switch (authorizationRefusal(error)) {
case 'forbidden': return this.translate.instant('pipeline.forbidden');
case 'unauthorized': return this.translate.instant('pipeline.unauthorized');
default: return null; // not an authorization problem — leave it to the caller
}The status is read structurally and must be a number, so a rethrown error that lost its
HttpErrorResponse prototype still classifies, while a body that merely carries "403" does not.
UI Components
LoginAppBarSectionComponent
A pre-built login/logout component for the app bar. Available via the secondary entry point @meshmakers/shared-auth/login-ui (requires Kendo UI).
import { LoginAppBarSectionComponent } from '@meshmakers/shared-auth/login-ui';
@Component({
imports: [LoginAppBarSectionComponent],
template: `
<mm-login-app-bar-section
[showRegister]="true"
(register)="onRegister()">
</mm-login-app-bar-section>
`
})Observability: session identity (AB#7327)
Opt-in: an app can tell its browser monitoring (Dash0 RUM) who is signed in and on which tenant. Apps that do
not opt in see no change, and @dash0/sdk-web is not required (it is an optional peer dependency used only by the
@meshmakers/shared-auth/dash0 entry point).
// app.config.ts — after the Dash0 SDK was initialised (init()) in main.ts
import { provideMmSharedAuth } from '@meshmakers/shared-auth';
import { provideDash0SessionIdentity } from '@meshmakers/shared-auth/dash0';
providers: [provideMmSharedAuth(), provideDash0SessionIdentity()]| Attribute | Value |
|-----------|-------|
| user.id | sub claim |
| user.name | user name |
| user.email | e-mail address (omitted when the identity has none) |
| octo.tenant.id | tenant of the current access token (same key as the backend services) |
- When: set after sign-in and after every tenant switch (in-place token exchange and redirect fallback), only when
the identity actually changed (a token refresh does not repeat it); cleared when the session ends (logout,
session_terminated,token_refresh_error, cross-tab logout, the reset before a redirect tenant switch). The page load before sign-in stays anonymous. - Never breaks auth: the sink is driven from one
effect()over the user and token-tenant signals; an exception in it is caught and logged as oneconsole.warnwith a fixed text (no name, no e-mail). - Privacy: name and e-mail are personal data and are sent to Dash0 (EU region). Only enable it in apps and environments where that is covered by the data protection information (decision 2026-10-22, AB#7326).
- Other monitoring tools: provide your own
SessionIdentitySink({ set(identity), clear() }) under theSESSION_IDENTITY_SINKtoken of the main entry point; the Dash0 adapter is just one implementation.
Configuration Options
interface AuthorizeOptions {
issuer?: string; // Identity Provider URL
redirectUri?: string; // Redirect after login
postLogoutRedirectUri?: string; // Redirect after logout
clientId?: string; // OAuth client ID
scope?: string; // OAuth scopes
showDebugInformation?: boolean; // Enable debug logging
sessionChecksEnabled?: boolean; // Enable session checks
wellKnownServiceUris?: string[]; // APIs that should receive auth token
}User Interface
interface IUser {
sub: string; // Subject (user ID)
name: string; // Display name
given_name: string | null; // First name
family_name: string | null; // Last name
email: string | null; // Email address
role: string[] | null; // User roles
idp: string; // Identity provider
}Roles
Pre-defined role constants:
import { Roles } from '@meshmakers/shared-auth';
if (authorizeService.isInRole(Roles.AdminPanelManagement)) {
// Admin access
}Roles.FileViewer / Roles.FileManagement gate the platform file system (browse+download / manage),
next to Roles.ReportingViewer / Roles.ReportingManagement (AB#6171 decision D5). They are seeded by
System.Identity.Bootstrap; a one-off identity migration grants them to every holder of the matching
Reporting role.
Detailed Documentation
See docs/README.md for complete API reference with usage examples.
Architecture
shared-auth/
├── src/ # Main entry point (@meshmakers/shared-auth)
│ ├── public-api.ts
│ └── lib/
│ ├── authorize.service.ts # Core OAuth2/OIDC service
│ ├── authorize.guard.ts # Functional route guards
│ ├── authorize.interceptor.ts # Functional HTTP interceptor
│ ├── roles.ts # Role enum
│ └── mm-login-app-bar-section/ # Component (internal, not exported)
│
└── login-ui/ # Secondary entry point (@meshmakers/shared-auth/login-ui)
└── src/
├── public-api.ts
└── mm-login-app-bar-section/ # LoginAppBarSectionComponent (Kendo-dependent)Entry Points
| Entry Point | Import Path | Description |
|-------------|-------------|-------------|
| Main | @meshmakers/shared-auth | Core auth service, guards, interceptor (no Kendo dependency) |
| Login UI | @meshmakers/shared-auth/login-ui | LoginAppBarSectionComponent (requires Kendo UI) |
| Dash0 | @meshmakers/shared-auth/dash0 | provideDash0SessionIdentity() (requires the optional peer @dash0/sdk-web) |
Dependencies
- Angular 21 (core, common/http, router)
- angular-oauth2-oidc v20 (OAuth2/OIDC client)
- @progress/kendo-angular-* (optional, only for login-ui entry point)
Build & Test
# Build
npm run build:shared-auth
# Lint
npm run lint:shared-auth
# Run tests
npm run test:shared-authDocumentation and Testing Standards
- All developer documentation must be written in English
- Every code change must include updated documentation — update README.md, CLAUDE.md, docs/README.md, or inline docs when adding, modifying, or removing features
- Unit tests and integration tests must be executed after every code change
- Existing tests must be updated when the behavior of tested code changes
- New tests must be added when new features, components, or services are implemented
- Never commit code with failing tests