The layer every Angular app writes by hand
Deriving reactive auth state, guarding routes by role, signing requests and keeping tokens out of storage — written once, and kept behind a port you can swap.
State as signals
authenticated(), claims(), roles() and profile() are signals, so templates update on login, logout and refresh — including in a zoneless application, where getter-based state never re-renders.
Routes guarded by role
A functional authGuard with anyOf and allOf requirements that accumulate down the route tree — so a protected section is configured once, at its root.
Tokens never persisted
Nothing is written to localStorage or sessionStorage, where an XSS payload could read it. Continuity comes from the provider's SSO cookie, and PKCE S256 is on by default.
An allowlist for bearer tokens
The interceptor signs only the requests you name, refreshing the token first when it is near expiry. A pattern matching nothing attaches nothing — it fails closed rather than leaking a token to a third-party host.
Provider-agnostic by design
Three signals and four methods is the whole port. Roles are normalised by the adapter, optional features are feature-detected rather than faked, and swapping identity provider stays a one-file change.
Tests without a server
withFakeAuth() swaps in an in-memory adapter — a real implementation of the same port, not a stub. No Keycloak server, no mocked window.location, same code paths.
One provider call, then read state like any other signal
No event subscriptions to wire up, no derived signals to maintain, and nothing in your components that names your identity provider.
1. Configure it in app.config.ts
provideAuth(
withKeycloak({
url: 'https://auth.example.com',
realm: 'my-realm',
clientId: 'my-app',
}),
);
2. Read the state in a component
@Component({
template: `@if (auth.authenticated()) {
Hello {{ auth.claims()?.name }}
}`,
})
export class Header {
protected readonly auth = inject(AuthService);
}
That template updates on login, logout and token refresh — in a zoneless application too.
Meanwhile authGuard protects your routes by role, and authTokenInterceptor signs your API calls with a token it refreshes before it expires.
Ready to drop it in?
Install the package, add provideAuth() and register one interceptor. Tokens stay in memory, PKCE is on by default, and your tests run without a Keycloak server.