Skip to main content

Introduction

Angular is signals-first and zoneless by default. Reading authentication state should look like reading any other piece of state in your application.

Today it doesn't. Authentication state lives on the identity provider's client object as plain getters, and deriving reactive state from it is left to you. Every application ends up writing the same derivation layer.

ngx-auth-client writes that layer once and gives you the state directly.

@Component({
template: `@if (auth.authenticated()) {
Hello {{ auth.claims()?.name }}
}`,
})
export class Header {
protected readonly auth = inject(AuthService);
}

That's the whole integration for a component. The setup is one provider call:

bootstrapApplication(App, {
providers: [provideAuth(withKeycloak({ url, realm, clientId }))],
});

Why signals matter here

In a zoneless application, getter-based state isn't merely inconvenient — it's invisible:

@if (keycloak.authenticated) { ... } // ✗ never updates — nothing schedules CD
@if (auth.authenticated()) { ... } // ✓ updates on login, logout, refresh

Zone.js used to paper over this by re-rendering after every async task. Without it, state read through a getter simply doesn't participate in change detection.

The shape of the library

Your application code depends on AuthService. AuthService depends on the AuthProvider port. Only the adapter knows which identity provider you use — so swapping or upgrading it stays a one-file change.

What it does

ConcernWhat the library provides
Auth stateauthenticated, claims, roles, profile — all signals
Route guardsA functional authGuard with role requirements that accumulate down the route tree
Bearer tokensAn interceptor that attaches the token to an allowlist of requests, refreshing it first when near expiry
RolesNormalised to string[] by the adapter, whatever shape the provider uses
CapabilitiesRegistration, account console, password change and profile loading — feature-detected, never faked
TestingA real in-memory adapter, so no test needs a Keycloak server
Provider-agnosticA small port at the auth boundary, with a Keycloak adapter shipped on top

What it deliberately does not do

  • It stores no tokens. Nothing is written to localStorage or sessionStorage. Session continuity comes from the provider's own SSO cookie via silent re-authentication. See Security.
  • It renders nothing. No login pages, no toasts, no redirect UI. The provider's hosted pages handle authentication; your application handles everything else.
  • It does not register the interceptor for you. Order matters relative to your other interceptors, so you wire it up in withInterceptors() yourself.
  • It does not block bootstrap. init() starts during bootstrap and authGuard awaits it, so unprotected shell UI paints immediately while protected routes stay safe.
  • It brings no identity-provider dependency in the core. keycloak-js is an optional peer dependency, needed only by the Keycloak adapter.

Design principles

State is signals, operations are promises. The dividing line is state versus action. authenticated and roles are state, so they're signals. getToken() performs a refresh — that's an action, so it's a promise.

The port stays honest. Not every provider hosts a sign-up flow or an account console. Rather than forcing adapters to stub methods they can't support, optional features are declared as capabilities the core feature-detects, with a matching can* signal so you can hide UI you cannot deliver.

Role normalisation belongs to the adapter. Providers disagree about where roles live — Keycloak splits them across realm_access and resource_access, Auth0 uses a namespaced custom claim, Cognito uses cognito:groups. The adapter resolves that; the core only ever sees string[].

Security decisions fail closed. The bearer allowlist attaches no token when its pattern matches nothing, rather than leaking one to an unintended host. The guard refuses activation on the server rather than rendering protected content into a response that might be cached.

When you should use something else

  • You need certified, generic OIDC across arbitrary providers → angular-auth-oidc-client. It is OpenID-certified, mature, and the right call if protocol breadth is your priority.
  • You're happy deriving state from events yourselfkeycloak-angular is well maintained and closer to the metal.
  • You're on Auth0 or Entra ID today → use their first-party SDKs. Adapters here are on the roadmap, but shipped beats planned.

Compatibility

Supports Angular 17 through 22. Every one of those majors is verified on each push against the packed tarball — type-checked with ngc, bundled through a production build, and executed in Node, for all three entry points. The upper bound is deliberate: a new major is added only once it has been validated.

See Installation for the full requirements.

Next steps