Skip to main content

Authentication Adapters

The @archibald/auth package uses an adapter pattern on the client side: the SessionClient delegates every auth operation (login, logout, refresh, user loading, login check) to an AuthAdapter. The adapter decides how to talk to the BFF's auth endpoints and where tokens live.

Built-in adapters

CookieAuthAdapter (web, default)

The default adapter when none is configured. Tokens live in secure, httpOnly cookies (nct/ncr) set by the server — the client never sees them. Supports both login protocols via the credentials union CombinedCookieAuthCredentials:

  • authProtocol: 'password' (default when omitted) → POST auth/login with the credentials as body.
  • authProtocol: 'oidc' → full-page redirect to GET auth/login, forwarding only the query params allowed by the SessionClient's oidcConfig.queryParamsWhitelist. See OIDC Login Flow.
import { SessionClient, CookieAuthAdapter, type OidcConfig } from '@archibald/auth';

const oidcConfig: OidcConfig = { queryParamsWhitelist: ['returnPath'] };
const sessionClient = new SessionClient({ adapter: CookieAuthAdapter, api, oidcConfig });

HeaderAuthAdapter

For clients that manage tokens themselves (header strategy): tokens are returned in the response body, persisted via the client's storage adapter, and sent as Authorization: Bearer <token>. Password protocol only; on a failed refresh it logs the client out.

import { SessionClient, HeaderAuthAdapter } from '@archibald/auth';

const sessionClient = new SessionClient({ adapter: HeaderAuthAdapter, storage: 'local', api });

NativeAuthAdapter (React Native / Expo)

Exported from the expo entry point only (@archibald/auth), since it depends on expo-auth-session. Runs the OIDC authorization code flow in an in-app browser and exchanges the code via POST auth/token; falls back to header-style password login for non-OIDC credentials. Pair it with a secure-store-backed storage adapter — see the OIDC setup guide.

MockAdapter

For unit tests. Accepts any non-empty username/password and returns a static token/user ({ token: 'TOKEN', refresh: 'REFRESH', user: { id: 'id' } }).

import { SessionClient, MockAdapter } from '@archibald/auth';

const sessionClient = new SessionClient({ adapter: MockAdapter, api });

Writing a custom adapter

Extend the abstract AuthAdapter<User, Credentials> and override the operation hooks. The constructor receives the owning SessionClient, the api instance (exposed as this.createRequest), and the optional OidcConfig.

import { AuthAdapter, type AuthResponse, type AuthCheckResponse } from '@archibald/auth';

class CustomAdapter<User extends DefaultUser> extends AuthAdapter<User, MyCredentials> {
/** Called by sessionClient.logIn() (useLogin hook). */
override async onLogIn(credentials?: MyCredentials): Promise<AuthResponse<User>> { ... }

/** Called by sessionClient.logOut() (useLogOut hook). */
override async onLogOut(): Promise<AuthResponse<User>> { ... }

/** Called by sessionClient.refresh(). */
override async onRefresh(): Promise<AuthResponse<User>> { ... }

/** Called when user data is requested (useUser hook). */
override async onGetUser(): Promise<User> { ... }

/** Logged-in check without loading user data (sessionClient.check()). */
override async onCheckLoggedIn(): Promise<AuthCheckResponse> { ... }
}

const sessionClient = new SessionClient({ adapter: CustomAdapter, api });

Every base method throws Method not implemented. — only override what your strategy needs, but the SessionClient will call all five during a normal session lifecycle.

Storage adapters

Independently of the auth adapter, the SessionClient has a storage option (StorageAdapter subclass, or the shorthands 'cookie' — the default — and 'local') used by header-style adapters to persist tokens:

  • CookieStorageAdapter — non-httpOnly cookies (default).
  • LocalStorageAdapterlocalStorage.
  • Custom — e.g. the shop template's app platform wraps expo-secure-store in a StorageAdapter<string> subclass.

The CookieAuthAdapter doesn't need storage for tokens (the server owns them), but SessionClient.loggedIn still consults storage.get('token').