feat(auth): OIDC login (browser SSO) — backend#110
Merged
Merged
Conversation
The last big auth slice: an external IdP mints openctl sessions via Authorization Code + PKCE, reusing the shipped session/cookie/RBAC machinery. OIDC only adds an identity source; everything downstream (session lookup, RBAC, cookie) is unchanged. - config: auth.oidc block (issuer, clientID, clientSecretFile, redirectURL, roleClaim, roleMapping, defaultRole, usernameClaim); ResolveClientSecret reads the 0600 secret file. - internal/controller/auth/oidc.go: OIDCAuthenticator over github.com/coreos/go-oidc/v3 + golang.org/x/oauth2 — discovery, PKCE AuthCodeURL, code exchange + ID-token verification (signature/iss/aud/ exp via the IdP JWKS), and claims->role mapping (highest matching group wins; deny-by-default when nothing matches and no defaultRole). - internal/controller/server/oidc_http.go: /auth/oidc/login (state + PKCE verifier in short-lived HttpOnly cookies -> redirect to IdP) and /auth/oidc/callback (verify state, exchange, map principal, mint session via SessionStore.Create, set the openctl_session cookie, redirect to /ui/). A denied user gets a clear 403, no session. - wiring: gateway mounts the routes only when auth.oidc.enabled; an unreachable issuer / unreadable secret is a fatal startup error. Coexists with the root token + named tokens; --no-auth unchanged. Adds deps go-oidc/v3 + oauth2 (now direct). Tests: fake IdP (oidc_test.go, real go-oidc verification) — happy path + group->role, highest-role-wins, deny-by-default, default-role, wrong-audience and expired rejection, bad-role-mapping construction error. Remaining (follow-ups): a UI "Log in with SSO" button (the backend is reachable directly at /auth/oidc/login) and validation against a real IdP.
astrojerms
added a commit
that referenced
this pull request
Jul 8, 2026
Finishes the OIDC feature (backend shipped in #110). The login page now probes an unauthenticated /auth/oidc/enabled route (registered only when OIDC is configured) and, when present, renders a "Log in with SSO" button linking to /auth/oidc/login. When OIDC is off the route 404s and the button stays hidden. - server: OIDCHandler.register mounts /auth/oidc/enabled (200 + {"enabled":true}); it is only registered when auth.oidc.enabled, so its presence is the availability signal. - ui: Login.svelte probes it on mount and conditionally shows the SSO button (with an "or" divider) alongside the bearer-token form. Tests: the enabled probe returns 200 + enabled:true and is mounted by register(). npm run check clean, 50 UI tests pass. Remaining OIDC work: validation against a real IdP.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The last big auth slice. An external IdP mints openctl sessions via Authorization Code + PKCE, reusing the shipped session/cookie/RBAC machinery — OIDC only adds an identity source; everything downstream is unchanged. Implements docs/oidc-design.md.
Config
How
internal/controller/auth/oidc.go—OIDCAuthenticatorovergo-oidc/v3+oauth2: discovery, PKCEAuthCodeURL, code exchange + ID-token verification (signature/iss/aud/exp via the IdP JWKS), and claims→role mapping (highest matching group wins; deny-by-default).internal/controller/server/oidc_http.go—/auth/oidc/login(state + PKCE verifier in short-lived HttpOnly cookies → redirect to IdP) and/auth/oidc/callback(verify state, exchange, map principal, mint session viaSessionStore.Create, set theopenctl_sessioncookie, redirect/ui/). Denied user → clear 403, no session.auth.oidc.enabled; an unreachable issuer / unreadable secret is a fatal startup error. Coexists with root token + named tokens;--no-authunchanged.Tests — fake IdP, real go-oidc verification
oidc_test.gostands up a discovery + JWKS + token endpoint signing ID tokens with a test key (the OIDC analog ofplugins/tf-fake): happy path + group→role, highest-role-wins, deny-by-default, default-role, wrong-audience + expired rejection, bad-role-mapping error. Full suite +-race, lint/staticcheck/modernize clean,make buildgreen. New direct deps:go-oidc/v3,oauth2.Remaining (follow-ups)
A UI "Log in with SSO" button (the backend is reachable directly at
/auth/oidc/login) and validation against a real IdP.