OIDC Login Flow
Since v9, Archibald supports login via OpenID Connect (OIDC) using the OAuth2 Authorization Code flow with PKCE. Instead of collecting a username and password in the storefront and exchanging them for a token (the legacy password flow), the user is redirected to the identity provider (IdP — e.g. the SAP Commerce authorization server), authenticates there, and is redirected back with an authorization code that the BFF exchanges for tokens.
Both flows share the same client API (SessionClient, useLogin, useLogOut, useUser, useIsLoggedIn), the same JWE session tokens (nct/ncr cookies in the cookie strategy), and the same refresh/logout endpoints. Which flow runs is decided per login call via credentials.authProtocol.
- For a step-by-step setup guide, see Using OIDC in your project.
- To migrate an existing username/password login, see Migrating from the password flow.
Enabling protocols: the whitelist
Auth endpoints are registered conditionally based on app.authentication.whitelistedProtocols (type AuthProtocol[], i.e. 'password' | 'oidc'):
// environment/common.ts
app: {
authentication: {
whitelistedProtocols: ['password', 'oidc']
}
}
| Endpoint | Handler | Registered when |
|---|---|---|
POST auth/login | AuthController.login | 'password' whitelisted |
GET auth/login | AuthController.loginUrl (302 to the IdP) | 'oidc' whitelisted |
POST auth/token | AuthController.token (code → token exchange, used by native) | 'oidc' whitelisted |
GET auth/callback | AuthController.redirectCallback | 'oidc' whitelisted |
POST auth/refresh | AuthController.refresh | always |
POST auth/logout | AuthController.logout | always |
GET auth/check | AuthController.check (mode: 'try') | always |
GET auth/user | AuthController.user (mode: 'required') | always |
auth/login is overloaded by HTTP method: POST is the password login, GET starts the OIDC redirect. With both protocols whitelisted (the shop template default) both handlers coexist. Paths are relative to your API schema — with the template's app.api config the effective URLs are /api/v2/auth/....
The same whitelist is enforced on the client: CookieAuthAdapter.onLogIn throws Protocol not supported for a non-whitelisted authProtocol.
Web flow (cookie strategy)
Key properties:
- Two full-page redirects. The SPA unloads when the flow starts;
SessionClient.logIn()returns{ success: true, redirect: true }and does not publish aloginevent or setloggedIn— the logged-in state materializes on the fresh SSR render after the callback redirect. - PKCE and state are handled server-side by the
UserAuthProvider(e.g.CommerceUserAuthProviderusingopenid-client):code_verifier,auth_state, andauth_return_pathare stored in short-lived (5 min)httpOnlycookies, validated and cleared in the callback. redirect_uridefaults to${app.canonicalBaseUrl}/api/v2/auth/callback— a correctcanonicalBaseUrlper environment is mandatory.- Session cookies (
nct,ncr) are set during the callback request viaAuthService.checkSignAndSetToken(), before the final redirect is issued. - Query param forwarding is allowlisted. Only params listed in the
SessionClient'soidcConfig.queryParamsWhitelist(e.g.['returnPath']) survive fromlogin()toGET auth/login.
Native flow (header strategy)
On React Native/Expo the redirect dance happens in an in-app browser via expo-auth-session, and the code exchange goes through POST auth/token:
login({ authProtocol: 'oidc', clientId, discoveryEndpoint, ... })→NativeAuthAdapter.onLogIn.- The adapter builds an
AuthRequest(responseType: Code,redirectUrifrommakeRedirectUri({ path: redirectPath })), resolves the discovery document (fetchDiscoveryAsync, falling back to a bareauthorizationEndpoint), and opens the system browser withpromptAsync. - On success the deep link returns a
code; the adapter posts{ code, code_verifier, redirect_uri, authProtocol: 'oidc' }toPOST auth/token. AuthService.token()→UserAuthProvider.token()exchanges the code. Withstrategy: { type: 'header' }the signed tokens come back in the JSON body and the adapter persists them to the clientstorage(secure store); middleware attachesAuthorization: Bearer <token>to subsequent requests.
PKCE on the native path is driven by expo-auth-session on the client (the code_verifier never leaves the device except in the exchange request); there is no server-side state cookie validation here.
Where the flows converge
AuthService.login() (password) and AuthService.token() (OIDC exchange) run the same internal pipeline, differing only in which provider hook is called and which authProtocol is stamped into the token claims:
UserAuthProvider.login(credentials)/.token(credentials)/.redirectCallback()returns provider tokens.- All registered
SystemAuthProviders log in and contribute their tokens. UserAuthProvider.loadUser(tokens)fetches the user.checkSignAndSetToken()signs everything into JWE session tokens and (cookie strategy) setsnct/ncr.
Because the authProtocol is stamped per claim and preserved on refresh, password- and OIDC-issued sessions refresh, validate, and log out identically.
Related pages
- Using OIDC in your project — full setup as done in the shop template.
- Migrating from the password flow
- Authentication Providers — the
UserAuthProvidercontract (loginUrl,redirectCallback,token). - Secure Token Handling — JWE tokens,
nct/ncr, refresh. - AuthModule — server-side options and endpoints.