0019 - Deployment-wide OIDC federated sign-in
- Status: Accepted
- Date: 2026-06-03
- Deciders: nexus core team
- Tags: auth, oidc, identity, console
Context and Problem Statement
Section titled “Context and Problem Statement”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.
Decision Drivers
Section titled “Decision Drivers”- 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.
Considered Options
Section titled “Considered Options”- Hand-rolled OIDC client — minimal dependencies, but reimplements JWKS rotation, discovery, and ID-token verification, which is security-sensitive.
openidconnectcrate, 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.
Decision Outcome
Section titled “Decision Outcome”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.
Consequences
Section titled “Consequences”- 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_deniedandauth.identity.linkedaudit 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.
