From bc19ad8804189ee66510514cae93bec198e3d4a6 Mon Sep 17 00:00:00 2001 From: Steve Cliff Date: Tue, 5 May 2026 12:56:16 +0100 Subject: [PATCH] =?UTF-8?q?spec:=20P4-05=20=E2=80=94=20Authelia-specific?= =?UTF-8?q?=20defaults?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Confirmed claim name from the lab IdP is 'groups' (not 'roles' as the original spec assumed). Default the role_claim config field to 'groups' which also matches Keycloak and Authentik out of the box. Add a 'display_name' field so the SSO button can read 'Sign in with Authelia' rather than the generic 'SSO'. Two new gotchas captured: - Authelia 4.39+ 'sub' is an opaque UUID, not username — the locked design already keys on sub + reads preferred_username for display, so this is just documentation. - end_session_endpoint isn't always published (Authelia config- dependent); the locked logout flow already degrades cleanly. --- docs/superpowers/specs/2026-05-05-p4-05-oidc-design.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/docs/superpowers/specs/2026-05-05-p4-05-oidc-design.md b/docs/superpowers/specs/2026-05-05-p4-05-oidc-design.md index eb9f9b7..f0cac64 100644 --- a/docs/superpowers/specs/2026-05-05-p4-05-oidc-design.md +++ b/docs/superpowers/specs/2026-05-05-p4-05-oidc-design.md @@ -19,7 +19,7 @@ The Authorization Code flow (with PKCE) is implemented against the discovered we | Decision | Pick | |---|---| | User lifecycle | **B** — JIT-provision local rows on first OIDC login (`auth_source='oidc'`, `oidc_subject`) | -| Role mapping config | **A** — YAML/env, claim name `roles`, default = deny on no-match | +| Role mapping config | **A** — YAML/env, claim name configurable (default `groups`, matching Authelia / Keycloak / Authentik), default = deny on no-match | | Username source | `preferred_username`, fallback to `email` | | Username collision with existing local user | **Refuse** with clear remediation message | | Provider config | **Single provider** — `providers:` array can come later | @@ -51,8 +51,9 @@ oidc: issuer: https://auth.example.com # well-known config discovered from this client_id: restic-manager client_secret: ${RM_OIDC_CLIENT_SECRET} # or via _FILE - scopes: [openid, profile, email, roles] # 'roles' usually means a custom scope - role_claim: roles # default if absent + display_name: Authelia # button label "Sign in with "; default "SSO" + scopes: [openid, profile, email, groups] + role_claim: groups # default if absent (matches Authelia / Keycloak / Authentik) role_mapping: rm-admins: admin rm-operators: operator @@ -196,6 +197,8 @@ The IdP is the hard part to test cleanly. Two layers: - **End-session 404 / not advertised** — degrade gracefully; just drop the session and 303 to `/login`. Don't 500 the logout because the IdP doesn't implement RP-initiated logout. - **Username changes at the IdP** — silently keep the local username (matches our locked decision: subject is the stable key, username is display-only). Document. - **Role claim is sometimes a string, sometimes an array, sometimes a comma-separated string** depending on IdP — normalise into `[]string` before mapping. Authelia/Keycloak emit arrays; some custom setups emit strings; handle both. +- **Authelia `sub` is an opaque UUID, not the username** (Authelia 4.39+ default for new clients). Don't assume `sub` is human-readable; it's stable but display value is `preferred_username` or `email`. The locked design already keys lookups on `sub` and uses `preferred_username` for the display username, so this is just a correctness note. +- **`end_session_endpoint` may not be published** (Authelia doesn't advertise it for many configs). The locked logout flow already degrades to "drop session + redirect to /login" when the discovery doc lacks it; no extra config needed. - **Password-form bypass for OIDC users via /api/auth/login (JSON)** — same rejection rule applies, not just the HTML form. ## Acceptance