Skip to main content

AuthService

The single service application code injects. Provided in root, so it needs no registration.

private readonly auth = inject(AuthService);

It wraps the configured AuthProvider and adds three things the raw port deliberately leaves out: initialisation state, capability detection, and role predicates.

Summary

MemberType
authenticatedSignal<boolean>
claimsSignal<Claims | null>
rolesSignal<readonly string[]>
profileSignal<UserProfile | null>
readySignal<boolean>
initErrorSignal<unknown>
canRegisterSignal<boolean>
canManageAccountSignal<boolean>
canUpdatePasswordSignal<boolean>
canLoadProfileSignal<boolean>
whenReady()Promise<void>
getToken()Promise<string>
login(options?)Promise<void>
logout(options?)Promise<void>
register(options?)Promise<void>
accountManagement()Promise<void>
updatePassword(options?)Promise<void>
loadProfile()Promise<UserProfile>
hasRole(role)boolean
hasAnyRole(roles)boolean
hasAllRoles(roles)boolean

State

authenticated

readonly authenticated: Signal<boolean>;

Whether a valid session currently exists. Forwarded straight from the adapter.

false during initialisation, before anything is known — pair it with ready() to tell "not signed in" from "not known yet".

claims

readonly claims: Signal<Claims | null>;

Decoded access-token payload, null when unauthenticated. Normalised to the standard OIDC claim names, with provider-specific claims reachable through an index signature. See Claims.

roles

readonly roles: Signal<readonly string[]>;

Roles held by the user, normalised by the adapter — never null, empty when there are none. Where they were read from is the adapter's decision; see Role sources.

profile

readonly profile: Signal<UserProfile | null>;

Profile loaded by loadProfile(). null until the first successful load, and on adapters without the capability it stays null forever. See UserProfile.

Initialisation state

ready

readonly ready: Signal<boolean>;

Whether initialisation has settled — successfully or not. false during the initial load.

@if (!auth.ready()) {
<app-spinner />
} @else if (auth.authenticated()) {
<app-shell />
} @else {
<app-landing />
}

initError

readonly initError: Signal<unknown>;

The error that failed initialisation, or null.

null after a normal start whether or not a session was found; non-null only when the adapter's init() threw — a misconfigured realm, a DNS failure, an unreachable provider. A failed init is not a crash: it means "not authenticated", and it's surfaced as state so an application with public pages still boots.

whenReady()

whenReady(): Promise<void>;

Resolves once the session has been restored, or definitively not restored.

Never rejects. Inspect initError() to distinguish "no session" from "the provider is unreachable".

await this.auth.whenReady();

if (this.auth.authenticated()) {
// …
}

Safe to await repeatedly — it returns the same promise every time.

Capabilities

Four signals reporting what the configured adapter supports. See Capabilities.

SignalGuards
canRegisterregister()
canManageAccountaccountManagement()
canUpdatePasswordupdatePassword()
canLoadProfileloadProfile()
@if (auth.canRegister()) {
<button (click)="auth.register()">Create an account</button>
}

Each is a computed() over a type guard that checks for the method's presence on the provider. Calling an unsupported capability rejects with UnsupportedCapabilityError.

Operations

getToken()

getToken(): Promise<string>;

Returns an access token valid now, refreshing it first if it's close to expiry.

Throws when the session cannot be renewed and the user must log in again.

Promise-based on purpose: a refresh is an operation, not state. Modelling it as a signal would mean either handing out a possibly-expired string, or exposing a loading flag beside it.

const token = await this.auth.getToken();

You rarely need this — authTokenInterceptor calls it for requests on the allowlist. Reach for it when signing a fetch() call or a WebSocket handshake.

login()

login(options?: LoginOptions): Promise<void>;

Sends the user to the provider's login page. The promise reflects the initiation of the redirect, not its completion — the page is navigating away.

await this.auth.login({
redirectUri: globalThis.location.href,
prompt: 'login', // force re-authentication
loginHint: 'user@example.com',
});

See LoginOptions. Throws on a non-browser platform.

logout()

logout(options?: LogoutOptions): Promise<void>;

Terminates the session.

await this.auth.logout({ redirectUri: 'https://app.example.com/goodbye' });

It cannot retract an already-issued access token — see Security.

register()

register(options?: RegisterOptions): Promise<void>;

Sends the user to the provider's sign-up page. Rejects with UnsupportedCapabilityError when canRegister() is false.

accountManagement()

accountManagement(): Promise<void>;

Sends the user to the provider's self-service account console. Takes no options — the provider decides where to return them. Rejects when canManageAccount() is false.

updatePassword()

updatePassword(options?: UpdatePasswordOptions): Promise<void>;

Sends the user to the provider's password-change flow. Defaults to returning them to the current page. Rejects when canUpdatePassword() is false.

loadProfile()

loadProfile(): Promise<UserProfile>;

Fetches the user profile and publishes it on profile. Rejects when canLoadProfile() is false.

const profile = await this.auth.loadProfile();
// …or read this.auth.profile() afterwards

Role predicates

All three read the roles signal, so they're reactive inside a computed() or a template.

hasRole()

hasRole(role: string): boolean;

hasAnyRole()

hasAnyRole(roles: readonly string[]): boolean;

Whether the user holds at least one. An empty list passes.

hasAllRoles()

hasAllRoles(roles: readonly string[]): boolean;

Whether the user holds every one. An empty list passes.

protected readonly canPublish = computed(() => this.auth.hasRole('editor'));
Reactive only inside a reactive context

Assigning hasRole('editor') to a plain field evaluates it once at construction and freezes it. Wrap it in computed().

Not authorisation

These are UX helpers. Enforce every requirement that matters server-side — see Security.

Next