Appearance
Architecture and domain boundaries
Sources of truth
| Owner | Owns |
|---|---|
| ZITADEL | Authentication identities, credential material, verification, MFA/passkeys, OIDC tokens, authentication sessions |
| Central Auth Gateway | Stable sub → local user mapping, canonical cross-platform profile, platform membership, linked accounts, dynamic onboarding values, soft deletion, legacy mappings, audit records, auth transactions, gateway sessions, handoffs |
| Platform BFF | Platform-local session after handoff exchange, final returnTo redirect, optional platform-hosted onboarding UI |
Email is mutable metadata, never an identity key. users.zitadel_subject is unique. JIT provisioning upserts the user and creates a transactional profile shell.
System shape
There is one ZITADEL OIDC application for the Central Gateway. Its only registered redirect URI is AUTH_CALLBACK_URL (default http://localhost:8080/v1/auth/callback). Platforms are rows in the platforms table, not ZITADEL apps.
Request processing (authenticated APIs)
- JOSE verifies a ZITADEL access token against cached remote JWKS, issuer, audience, algorithm, expiry, and token type — or an opaque gateway session is read from the configured HttpOnly cookie.
- User endpoints provision/load the local user from
sub(Bearer) or from the gateway session’suserId(cookie) and reject inactive accounts. - Route handlers validate strict payloads and delegate to domain services.
- Services perform authorization-sensitive and multi-table changes transactionally.
- Fastify emits structured JSON logs with authorization and credential fields redacted.
User APIs (profile, account, onboarding values) accept either:
Authorization: Bearer <ZITADEL access token>, or- the gateway session cookie (
AUTH_SESSION_COOKIE_NAME, defaultiesf_auth_session)
Cookie-authenticated writes require an Origin header that is listed in CORS_ORIGINS. Platforms that call Central with the exchanged gatewayToken must forward it as that cookie; the gateway does not accept the opaque token as a Bearer credential.
Central login and handoff boundary
Authentication is never ZITADEL callback → platform returnTo. Platforms redirect browsers to the public Central Gateway start endpoint. The gateway validates the active platform, server-registered onboarding policy, and allowlisted return URL, then persists random state, nonce, and an S256 PKCE verifier. State and nonce are stored as hashes; recovery values are AES-256-GCM encrypted with AUTH_TRANSACTION_SECRET.
After ZITADEL returns to the central callback, the gateway:
- Atomically consumes state
- Exchanges the authorization code with the stored PKCE verifier
- Validates the signed ID token and nonce, and verifies the access token
- JIT-provisions the local user and upserts platform membership
- Evaluates the current requirement version
- Creates a short-lived HttpOnly gateway session
- Either redirects to Account Center (incomplete
centralonboarding) or issues a one-timehandoff_code to the platform’s registeredcallback_url
The platform BFF authenticates to Central with its own Basic credential, consumes the handoff once, and builds a local session. No ZITADEL token crosses that boundary.
Dynamic onboarding (gateway-owned)
onboarding_field_definitions stores PROFILE_FIELD references and CUSTOM_FIELD definitions. Canonical profile values stay in typed columns; custom values use JSONB. Schema create/update/archive increments platforms.requirement_version and moves existing COMPLETE memberships to IN_PROGRESS. Completion is always recalculated server-side; client flags such as completed=true are ignored.
Reliability
- Application instances keep no process-local session state and scale horizontally.
- Auth transactions, gateway sessions, and handoffs live in PostgreSQL.
- Migrations serialize with a PostgreSQL advisory lock.
/health/livehas no dependency checks;/health/readychecks PostgreSQL.- SIGTERM stops accepting work and drains in-flight requests.
- Avatar uploads use presigned PUTs directly between client and S3-compatible storage.