feat(auth): sign in with OpenID Connect providers #14

Merged
ryan.mcgee merged 37 commits from feat/oidc into main 2026-10-15 18:08:10 +00:00
Owner

Summary

  • Lets people sign in with an OpenID Connect provider (Keycloak, Entra ID, Okta, Google, Authentik, ...), configured server-wide by server admins.
  • Stacked on #13 (feat/alerting); first of three auth changes (OIDC, then email verification, then 2FA / WebAuthn).

Changes

  • TraefikControl.Secrets: the alert-secret encryption extracted and keyed by purpose; existing alert secrets still decrypt (tested against the old derivation).
  • identity_providers and user_identities tables, users.has_password (portable migration; round trip with data verified on SQLite and PostgreSQL).
  • TraefikControl.Identity: authorization code flow with PKCE (S256), state and nonce, using oidcc (new dependency, with jose and telemetry_registry). The ID token's signature, issuer, audience, expiry and nonce are verified; https certificates are always verified.
  • Account resolution: a known sub signs in; a signed-in user in sudo mode can link; a new account needs a verified email in an allowed domain and an allowing provider; an email that already has an account is refused, never linked.
  • Accounts made this way have no password until the person sets one; unlinking or removing a provider is refused when it would leave someone no way in.
  • /auth/oidc/:slug, /callback, /link; pending request is single use, 10 minutes, bound to its provider; failures are logged by shape only (no claims or nonces).
  • Admin page /admin/identity-providers (server admins, recent login required), sign-in buttons, linked accounts in settings, member.joined audit event for automatic organization membership.
  • TRAEFIK_CONTROL_OIDC_ALLOW_HTTP for development providers; README updated.

Verification

  • mix format --check-formatted, mix compile --warnings-as-errors, mix credo --strict, mix dialyzer: clean.
  • mix test: 1154 passed on SQLite and on PostgreSQL. New tests use a real local OIDC provider (Bandit, RSA JWKS) that enforces client credentials, redirect URI, single-use codes and PKCE.
  • Smoke test against ghcr.io/navikt/mock-oauth2-server with the app in a browser: provider added in the admin page, account created on first sign-in, linked account listed in settings, unlinking the only way in refused, linking an identity owned by another user refused. This found that oidcc requires scopes_supported, which that server omits; handled and covered by the fake provider.
## Summary - Lets people sign in with an OpenID Connect provider (Keycloak, Entra ID, Okta, Google, Authentik, ...), configured server-wide by server admins. - Stacked on #13 (`feat/alerting`); first of three auth changes (OIDC, then email verification, then 2FA / WebAuthn). ## Changes - `TraefikControl.Secrets`: the alert-secret encryption extracted and keyed by purpose; existing alert secrets still decrypt (tested against the old derivation). - `identity_providers` and `user_identities` tables, `users.has_password` (portable migration; round trip with data verified on SQLite and PostgreSQL). - `TraefikControl.Identity`: authorization code flow with PKCE (S256), state and nonce, using `oidcc` (new dependency, with `jose` and `telemetry_registry`). The ID token's signature, issuer, audience, expiry and nonce are verified; https certificates are always verified. - Account resolution: a known `sub` signs in; a signed-in user in sudo mode can link; a new account needs a verified email in an allowed domain and an allowing provider; **an email that already has an account is refused, never linked**. - Accounts made this way have no password until the person sets one; unlinking or removing a provider is refused when it would leave someone no way in. - `/auth/oidc/:slug`, `/callback`, `/link`; pending request is single use, 10 minutes, bound to its provider; failures are logged by shape only (no claims or nonces). - Admin page `/admin/identity-providers` (server admins, recent login required), sign-in buttons, linked accounts in settings, `member.joined` audit event for automatic organization membership. - `TRAEFIK_CONTROL_OIDC_ALLOW_HTTP` for development providers; README updated. ## Verification - `mix format --check-formatted`, `mix compile --warnings-as-errors`, `mix credo --strict`, `mix dialyzer`: clean. - `mix test`: 1154 passed on SQLite and on PostgreSQL. New tests use a real local OIDC provider (Bandit, RSA JWKS) that enforces client credentials, redirect URI, single-use codes and PKCE. - Smoke test against `ghcr.io/navikt/mock-oauth2-server` with the app in a browser: provider added in the admin page, account created on first sign-in, linked account listed in settings, unlinking the only way in refused, linking an identity owned by another user refused. This found that oidcc requires `scopes_supported`, which that server omits; handled and covered by the fake provider.
- move Alerts' encrypt/decrypt into TraefikControl.Secrets, keyed
  by purpose, so identity providers can reuse it
- existing alert secrets still decrypt (same derived keys)
- server admins add providers (issuer, client, allowed email domains,
  optional organization for new accounts); secrets stored encrypted
- authorization code flow with PKCE, state and nonce via oidcc; ID
  token signature, issuer, audience, expiry and nonce are verified
- people are recognized by subject, never email; an email that already
  has an account is refused, users link providers in their settings
- accounts created through a provider have no password until set
- sign-in buttons, linked accounts in settings, admin page, audit
  event member.joined for automatic organization membership
- OIDC_ALLOW_HTTP for development providers; scopes_supported optional
ryan.mcgee changed target branch from feat/alerting to main 2026-10-15 18:08:04 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
White_Ghost_Cyber_Systems/traefik-control!14
No description provided.