Skip to content

Architecture and domain boundaries ​

Sources of truth ​

OwnerOwns
IESF identity service (identity-api + identity-web)Authentication identities, credentials (password, Google link), e-mail verification, identity sessions, OIDC tokens; one OIDC issuer per environment (PROD, DEV, TEST, …)
Central Auth Gateway(issuer, subject) → local user mapping, local roles, canonical cross-platform profile, platform membership, linked accounts, dynamic onboarding values, soft deletion, legacy mappings, audit records, auth transactions, gateway sessions, handoffs
Platform BFFPlatform-local session after handoff exchange, final returnTo redirect, optional platform-hosted onboarding UI

Email is mutable metadata, never an identity key. A user is identified by the pair (users.identity_issuer, users.identity_subject), which is unique; primary_email is not. The same e-mail address signing in through DEV and through PROD is therefore two different users. JIT provisioning upserts the user and creates a transactional profile shell.

System shape ​

An identity environment is one OIDC issuer (for example PROD https://identity.iesf.gg/api/auth, DEV https://dev.identity.iesf.gg/api/auth) plus the gateway's OIDC client in it and an optional post-logout redirect URI, stored in identity_environments. The read-only PROD row (source: deployment) stands for the IDENTITY_* deployment configuration; further environments (for example DEV, TEST) are created through /v1/identity-environments. Every environment's gateway client registers the same redirect URI, AUTH_CALLBACK_URL (default http://localhost:8080/v1/auth/callback). Platforms are rows in the platforms table, not identity clients; each platform is bound to exactly one environment, and a product that needs DEV and PROD registers two platforms with separate credentials.

Only the deployment (PROD) environment can grant gateway administration permissions (platforms:admin, onboarding:schema:*). They come from the local user_roles table (role platform_admin) for users, and from the token scope for machine clients. Users and machine tokens from any other environment never carry permissions. See Identity operations.

Each platform has one or more redirect pairs (platform_redirect_pairs): a return URL root paired with the BFF callback that receives the handoff for logins whose returnTo falls under that root.

Request processing (authenticated APIs) ​

  1. A Bearer JWT access token is verified — its iss selects an active identity environment, then JOSE checks that environment's cached JWKS, audience (IDENTITY_ACCESS_TOKEN_AUDIENCE), algorithm (EdDSA/RS256/ES256), and expiry; ID tokens are rejected — or an opaque gateway session is read from the configured HttpOnly cookie.
  2. User endpoints provision/load the local user by (issuer, sub) (Bearer) or from the gateway session's userId (cookie) and reject inactive accounts. Machine tokens are refused on user endpoints (403).
  3. Route handlers validate strict payloads and delegate to domain services.
  4. Services perform authorization-sensitive and multi-table changes transactionally.
  5. Fastify emits structured JSON logs with authorization and credential fields redacted.

User APIs (profile, account, onboarding values) accept either:

  • Authorization: Bearer <identity access token> (requested with resource=<IDENTITY_ACCESS_TOKEN_AUDIENCE>), or
  • the gateway session cookie (AUTH_SESSION_COOKIE_NAME, default iesf_auth_session)

Cookie-authenticated writes require an Origin header that is listed in CORS_ORIGINS, is the host of the deployment issuer or of an active identity environment's issuer (the identity UI renders central onboarding), or is the origin of a redirect pair's return URL on an active platform. The same allowlist backs the CORS layer, so registering a platform's return URLs is enough to let its browser calls through. 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 identity callback → platform returnTo. Platforms redirect browsers to the public Central Gateway start endpoint. The gateway validates the active platform, server-registered onboarding policy, and the redirect pair matching returnTo, then persists random state, nonce, and an S256 PKCE verifier together with the pair's callback and the platform's identity environment. State and nonce are stored as hashes; recovery values are AES-256-GCM encrypted with AUTH_TRANSACTION_SECRET.

After the identity service returns to the central callback, the gateway:

  1. Atomically consumes state
  2. Exchanges the authorization code at the environment's token endpoint (client_secret_basic, stored PKCE verifier)
  3. Validates the signed ID token (exact issuer, audience = client ID, nonce); the access token in the token response is ignored
  4. Finds or creates the local user by (issuer, sub) and upserts platform membership
  5. Evaluates the current requirement version
  6. Creates a short-lived HttpOnly gateway session (permission snapshot from local roles, PROD only; encrypted ID token kept only as logout hint)
  7. Either redirects to the identity UI's onboarding page (a required global profile field is missing, in any mode; or, in central mode, a required platform field) or issues a one-time handoff_ code to the callback of the redirect pair matched at start

The platform BFF authenticates to Central with its own Basic credential, consumes the handoff once, and builds a local session. No identity 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/live has no dependency checks; /health/ready checks PostgreSQL.
  • SIGTERM stops accepting work and drains in-flight requests.
  • Avatar uploads use presigned PUTs directly between client and S3-compatible storage.