8.1 KiB
OIDC Scope And Group Authorization Fix Spec
Status
Resolved on 2026-07-07; see Resolution. Reporter retest still requires a release artifact containing both fixes.
Primary issue: #1535
Related issues: #1528, #1533
Governed owners:
api-contracts: SSO provider API payloads, OIDC login initialization, callback authorization, session identity.frontend-primitives: Settings -> Security -> Single Sign-On provider configuration.
This document is a handoff spec. It is not a solution design.
User-Visible Failure
Settings-configured OIDC can complete part of the login flow, but group-based authorization and role mapping still fail for reporters using v6.0.4 through v6.0.5-rc.3.
The visible failure reported on #1535 after v6.0.5-rc.3:
- the displayed username/session label is no longer the internal
sso:oidc:...principal - group authorization still fails with
Your account is not part of an authorized group to use Pulse. - the observed OIDC authorization request scope is only
openid profile email - the reporter expects a configured group claim to be available for group role mapping
Reported IdPs:
- Pocket ID
- Authentik
What Is Already Fixed
The following commits are present on origin/main and in the v6.0.5 RC line:
caa9b41834Fix OIDC provider detail persistence- Fixes SSO provider API/detail persistence for nested OIDC fields, groups claim, allowed groups, and group role mappings when the payload supplies them.
- References #1521.
1c8a9346efFix legacy OIDC SSO discovery and CSP nonce- Restores the saved/legacy OIDC discovery path and SSO button behavior.
- References #1533.
eb99d7a6b3Fix SSO session display labels- Keeps the provider-scoped SSO principal as the stable session owner while displaying the IdP username/email/display claim in app chrome.
- References #1535.
These commits do not finish group-scope authorization for Settings-configured OIDC.
Current Evidence
Current origin/main evidence:
internal/api/identity_sso_handlers.go- OIDC provider detail responses expose nested OIDC scopes.
- Create/update handlers can persist supplied OIDC scopes, groups claim, allowed groups, and group role mappings.
internal/api/sso_handlers_crud_test.go- API tests prove a payload containing
["openid", "profile", "email", "groups"]can round-trip through provider detail and persistence.
- API tests prove a payload containing
frontend-modern/src/components/Settings/ssoProvidersModel.ts- The form model has
oidcScopes. - The empty form default is
openid profile email. - The payload builder sends
oidc.scopesfromform.oidcScopes.
- The form model has
frontend-modern/src/components/Settings/SSOProvidersPanel.tsx- The OIDC create/edit UI exposes issuer, client, secret, redirect/logout, groups claim, allowed groups, allowed domains, allowed emails, and group role mappings.
- The OIDC create/edit UI does not render an editable OIDC scopes field.
internal/api/oidc_handlers.go- Login initialization falls back to
openid profile emailwhen provider scopes are empty. - Group restriction and group role mapping depend on the configured groups claim being present in the OIDC claims.
- Group claim extraction already accepts arrays and comma/space-separated strings.
- Login initialization falls back to
Commit history evidence:
- No commits from
v6.0.0..origin/maintouchfrontend-modern/src/components/Settings/SSOProvidersPanel.tsx. - No commits from
v6.0.0..origin/maintouchfrontend-modern/src/components/Settings/ssoProvidersModel.ts.
Expected Product Behavior
An administrator configuring OIDC through Settings must be able to view and edit the exact OIDC scopes Pulse uses for the authorization request.
The configured scopes must be the same scopes that:
- are saved by provider create/update
- are returned by provider detail
- are shown again when the provider is reopened for editing
- are used when Pulse builds the OIDC authorization request
For providers with no custom scopes configured, existing behavior remains:
- default scopes are
openid profile email - existing providers continue to work without requiring manual reconfiguration
For providers that require an extra group scope before returning group claims:
- a Settings-configured provider can request that scope
- Pulse can receive the configured group claim
- allowed-group checks use that claim
- group role mappings use that claim
- a matching group grants the mapped Pulse role
- a non-matching or missing group still fails closed
The v6 SSO identity invariant remains:
- the provider-scoped subject is the stable SSO principal
preferred_username, email, or display name must not become the canonical session owner- local-user linking by
preferred_usernamemust not be reintroduced as the authorization model - app chrome should continue to display the IdP user-facing claim instead of the internal provider-scoped principal
Non-Goals
This fix does not need to redesign SSO.
This fix does not need to change:
- SAML behavior
- proxy auth
- local username/password auth
- paid-tier gating
- the provider-scoped SSO principal model
- the visible v6 Settings navigation model
The callback/auth-routing failure reported in #1533 is related OIDC fallout, but it is distinct from the missing group-scope path unless evidence proves a shared root cause.
Not Fixed If
The issue is not fixed if any of the following remain true:
- the API accepts custom OIDC scopes, but the Settings UI cannot configure them
- the Settings UI has a groups claim field, but the authorization request still omits the configured group scope
- Pulse globally adds
groupsto every OIDC provider without preserving admin-configured scope intent - role mapping works only when the IdP happens to return groups under the default
openid profile emailrequest - role mapping depends on matching a local Pulse user by
preferred_username - the display label regresses to
sso:oidc:... - existing providers with no custom scopes break
- reporter retest is requested before a release artifact actually contains the fix
Required Proof
A complete fix needs proof for these outcomes:
- A Settings-created OIDC provider can save a non-default scope set such as
openid profile email groups. - Reopening that provider in Settings shows the same scope set.
- The authorization request generated for that provider includes the saved scope set.
- An OIDC callback containing the configured groups claim grants the mapped role.
- An OIDC callback without a matching group still fails closed.
- Existing providers with empty or missing scopes still use
openid profile email. - The #1535 display-label fix remains intact.
- The #1533 SSO button/discovery fix remains intact.
- Tests cover both backend payload persistence and the Settings UI path that a normal administrator uses.
- Browser proof exercises the Settings -> Security -> Single Sign-On create/edit path, not only source-level payload builders.
Resolution (2026-07-07)
Two defects, two repos:
- repos/pulse
87aac4e57adds the editable Scopes field to the Settings OIDC create/edit modal (the form model already round-trippedoidc.scopes; the panel never rendered an input for it), plus model and panel tests covering the create/edit round-trip. - pulse-enterprise
689100cfixes the deeper root cause. SSO admin endpoints are overridden by the enterprise binder (pulse-enterprise/internal/ssoadmin/hooks.go), and its provider detail GET used a local flat serialization that drops nested OIDC scopes, the groups claim, and group role mappings. Because SSO is license gated, every real install reads provider detail through that override, so reopening a provider showed defaults and the next save reset the saved scopes. The OSS-side persistence/detail fixes incaa9b41834never executed on licensed builds. Detail reads now delegate to the core handler, which returns the canonical nested payload.
Verified live against a dev enterprise build: a provider created with
openid profile email groups shows the same set when reopened, and the
/api/oidc/{id}/login redirect carries
scope=openid+profile+email+groups. Persistence and the authorization
request were already correct end to end; only the detail read path was
lossy.