feat(dashboard_auth): support confidential clients (client_secret) in self-hosted OIDC (#55344)
The self-hosted OIDC dashboard provider was public-client + PKCE only, with two `# TODO(confidential-client)` seams. Authentik and Keycloak commonly default a new OIDC client to *confidential*, whose token endpoint rejects an unauthenticated exchange (`invalid_client`) — so a self-hoster who accepts their IDP's default could not complete dashboard login without manually flipping the client to public. Add optional confidential-client support: - New optional `client_secret` (env `HERMES_DASHBOARD_OIDC_CLIENT_SECRET`, or `dashboard.oauth.self_hosted.client_secret`; env-wins-config, empty treated as unset). It is a credential, so docs steer operators to the `.env` file; config.yaml is supported only for precedence symmetry. - `_token_endpoint_auth()` selects `client_secret_basic` (HTTP Basic header) vs `client_secret_post` (form body) from the IDP's advertised `token_endpoint_auth_methods_supported`, defaulting to basic (the OIDC default) when absent. Applied to complete_login, refresh_session, and revoke_session (RFC 7009 §2.1). - PKCE is sent in BOTH modes — the secret is client authentication layered on top, never a replacement (OAuth 2.1 / RFC 9700 keep PKCE mandatory). - Basic header url-encodes client_id/secret before base64 per RFC 6749 §2.3.1, so reserved chars (`:`, `@`, space) round-trip correctly. Non-breaking: with no secret configured the provider is a pure public PKCE client, byte-identical to prior behaviour (no Authorization header, no client_secret in the body). The secret is never logged — register() reports only a `confidential=<bool>` flag. Tests: 16 new cases covering basic/post selection, default-when-absent, public-unchanged contract, PKCE-preserved, reserved-char url-encoding, blank-secret-is-public, refresh + revoke auth, no-secret-in-logs, and env/config register wiring. Full dashboard-auth suite (nous provider, middleware, gate, cookies, WS, 401-reauth, status endpoint) — 396 tests — green, proving no existing auth path regressed.
This commit is contained in:
parent
481caa66f2
commit
53a75f147f
3 changed files with 446 additions and 22 deletions
|
|
@ -1276,3 +1276,33 @@ updates:
|
|||
# # default — works on Fly.io out of the box).
|
||||
# #
|
||||
# # public_url: "https://example.com/hermes"
|
||||
#
|
||||
# -----------------------------------------------------------------------------
|
||||
# Self-hosted OIDC dashboard auth (generic OpenID Connect — Authentik,
|
||||
# Keycloak, Zitadel, Authelia, Auth0, Okta, Google, …). Use this INSTEAD of the
|
||||
# nous block above when gating the dashboard with your own identity provider.
|
||||
# Each setting can be overridden by an environment variable:
|
||||
#
|
||||
# dashboard.oauth.self_hosted.issuer <- HERMES_DASHBOARD_OIDC_ISSUER
|
||||
# dashboard.oauth.self_hosted.client_id <- HERMES_DASHBOARD_OIDC_CLIENT_ID
|
||||
# dashboard.oauth.self_hosted.scopes <- HERMES_DASHBOARD_OIDC_SCOPES
|
||||
# dashboard.oauth.self_hosted.client_secret <- HERMES_DASHBOARD_OIDC_CLIENT_SECRET
|
||||
#
|
||||
# dashboard:
|
||||
# oauth:
|
||||
# provider: self-hosted
|
||||
# self_hosted:
|
||||
# issuer: "https://auth.example.com/application/o/hermes/" # required
|
||||
# client_id: "hermes-dashboard" # required
|
||||
# scopes: "openid profile email" # optional
|
||||
#
|
||||
# # OPTIONAL — set ONLY if your IDP registered the client as
|
||||
# # *confidential* (Authentik / Keycloak often default to this). When
|
||||
# # set, Hermes authenticates the client at the token endpoint
|
||||
# # (client_secret_basic or client_secret_post, auto-selected from the
|
||||
# # IDP's discovery doc) IN ADDITION to PKCE. Leave unset for a public
|
||||
# # (PKCE-only) client — the common case.
|
||||
# #
|
||||
# # This is a CREDENTIAL: prefer setting HERMES_DASHBOARD_OIDC_CLIENT_SECRET
|
||||
# # in ~/.hermes/.env over putting it here in config.yaml.
|
||||
# # client_secret: ""
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue