feat(canopy-auth): provider-agnostic OIDC (discovery + neutral naming)
Pre-existing tech debt: canopy-auth + canopy-web hardcode Keycloak-specific OIDC paths (/protocol/openid-connect/{auth,token,logout,certs}) and use keycloak_* config keys throughout. This bakes Keycloak as the only supported IdP — Okta, Auth0, Azure AD users would have to fork the codebase.
The standard fix is OIDC .well-known/openid-configuration discovery: every compliant OIDC provider exposes the metadata document at the issuer URL, with the actual endpoint URLs inside. Use that, drop the hardcoded paths.
Acceptance criteria
- New
crates/canopy-auth/src/discovery.rsmodule withOidcDiscovery::fetch(issuer_url)— caches per-URL withCache-Control: max-agehonoured. -
JwksProvider::from_discovery(discovery)async constructor; legacyJwksProvider::new(issuer)deprecated (drops the/protocol/openid-connect/certsKeycloak fallback) and removed in the follow-up MR (#411 (closed) / B1). -
ServiceSettings.keycloak_issuer→oidc_issuerrename (with#[serde(alias)]for one MR cycle of back-compat). -
services/canopy-web/src/auth.rs5 hardcoded path strings replaced withdiscovery.{authorization,token,end_session}_endpointreferences (browser-visible discovery cached separately from server-side discovery). -
docker-compose.ymlenv vars renamed (CANOPY_<SERVICE>__KEYCLOAK_*→CANOPY_<SERVICE>__OIDC_*, ~34 lines). - New
docs/modules/ROOT/pages/idp-integration.adocdocumenting Keycloak default + Okta/Auth0 configuration. - Test fixtures cover Keycloak / Okta / Auth0 discovery doc shapes.
- Existing
cargo xtask e2ebaseline still passes (regression-free).
Out of scope
- Removing the back-compat
#[serde(alias)]and the deprecatedJwksProvider::new— those drop in the dependent MR (#411 (closed) BFF token refresh) once both have shipped together. - Azure AD smoke test — documented as untested. File a follow-up if a tenant becomes available.
- Service-account / client-credentials flow — separate concern, file when needed.
Why priority::high
Blocks #411 (closed) (BFF token refresh — the actual production bug). Without this refactor, the #411 (closed) fix would extend Keycloak coupling instead of using RFC 6749 refresh_token against a discovered token endpoint.
Plan
docs/modules/ROOT/pages/plans/oidc-pluggability-refactor.adoc (filed alongside this issue per ADR-013).
Surfaced by
User direction during Tier A planning, 2026-05-05: "we're already touching the code in that area, do the strategic refactor now."