Skip to content

0019 - Deployment-wide OIDC federated sign-in

  • Status: Accepted
  • Date: 2026-06-03
  • Deciders: nexus core team
  • Tags: auth, oidc, identity, console

Nexus authenticates console users with email/password sessions. Operators running a deployment for a company need their users to sign in with an existing identity provider (Google Workspace, Microsoft Entra ID) and need to control who may create an account. Per-workspace SSO with subdomain routing is a later, subscription-gated capability; the immediate need is a single deployment-wide set of providers and a registration policy.

  • Reuse the existing user, identity, session, membership, and audit primitives rather than building a second provisioning path.
  • Keep secrets and TLS aligned with Rust microservices with axum + tonic for backend: rustls with the aws-lc-rs provider, no native-tls/openssl/ring.
  • Configuration must be deployment-wide and operator-controlled now, without precluding per-workspace connections later.
  • The browser must only ever talk to the console origin and the IdP so the session cookie lands on the console as it does for password login.
  • Hand-rolled OIDC client — minimal dependencies, but reimplements JWKS rotation, discovery, and ID-token verification, which is security-sensitive.
  • openidconnect crate, env-driven providers — a maintained library for Authorization Code + PKCE, discovery, and ID-token verification, configured from the environment.
  • Per-workspace DB-backed connections with KMS-wrapped secrets now — the eventual model, but heavier than the current need and entangled with subdomain routing and subscription tiers.

Chosen option: openidconnect crate with env-driven, deployment-wide providers.

The auth service resolves providers from the environment behind an OidcProviderRegistry (Google and Microsoft built-in profiles over the generic OIDC discovery path). Each login runs Authorization Code + PKCE: the auth service builds the authorization URL and persists single-use state (hashed, short-TTL) in oidc_login_states; on callback it exchanges the code, verifies the ID token (signature, issuer, audience, expiry, nonce), and normalizes claims. Google requires a verified email; Microsoft requires the tid claim to match the configured tenant. Optional email-domain allowlists are enforced server-side.

Identity resolution reuses user_identities(issuer, subject): an existing linked identity always logs in; otherwise a verified email may link to an existing user (when AUTH_OIDC_LINK_BY_EMAIL is set) or just-in-time create a passwordless user, gated by AUTH_ALLOW_REGISTRATION (true / invite / false). New users provision through the existing Personal Workspace bootstrap. The session JWT carries amr reflecting the authentication method.

The console proxies the flow: a browser hits a console route that calls the auth service, redirects to the IdP, and the IdP returns to a console callback route that exchanges the result and sets the existing session cookies. The openidconnect crate uses its rustls-tls feature, keeping TLS off native-tls.

  • Operators configure providers and registration policy entirely from the environment; client secrets are not stored in the database for this slice.
  • Federated users and memberships flow through the same primitives as password users, so RBAC, audit, and data-plane authorization are unchanged.
  • OIDC lifecycle steps emit auth.oidc.login_started / login_succeeded / login_denied and auth.identity.linked audit events.
  • Per-workspace OIDC connections (DB + KMS-wrapped secrets), subdomain-based SSO entrypoints, SCIM, and SAML remain deferred; the registry boundary lets them be added without changing the protocol code.