You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Wire identityFromToken into the OAuth2 upstream provider
Description
Phase 3 of the Snowflake / identityFromToken story (#5150). Connect
the pure helper (#5152) and the CRD field (#5155) into the embedded
auth server's runtime: extract identity from the raw token-endpoint
response body and use it as the resolved upstream identity, sibling
to the existing userInfo and PR #5094 synthesis paths.
Context
The existing token-response rewriter already reads and parses every
successful token-endpoint response in order to normalise non-standard
envelopes (e.g. GovSlack's nested fields). Extend it to also extract
user identity claims from the same body when an IdentityFromTokenConfig is configured on OAuth2Config.
Identity extraction runs on the RAW pre-rewrite body, so identity
paths are resolved against the original provider response even when tokenResponseMapping is also configured (Slack v2 needs both: the
user-level access token nested under authed_user.access_token and
the user identifier under authed_user.id).
The new priority chain in BaseOAuth2Provider.ExchangeCodeForIdentity:
IdentityFromToken — when configured, return the extracted
identity. If extraction failed (path didn't resolve), return ErrIdentityResolutionFailed without falling back to userInfo
or synthesis — "identityFromToken set" is an explicit operator
declaration that the token response is the identity source, and
silently falling through would mask misconfiguration.
The refresh path passes nil for the identity config, because providers
like Snowflake omit username on refresh; identity is captured once
at auth-code time and persisted into session storage by the callback
handler.
OIDC providers always have an ID-token-derived subject and discard
the rewriter's identityFromToken return value. A defence-in-depth
WARN fires if a future config-loader bug ever sets IdentityFromToken
on an OIDC base config (the field is structurally absent on the OIDC
CRD type today).
A tripwire test asserts the userinfo HTTP endpoint is never contacted
when identityFromToken is configured — including on extraction
failure. This is the single most important security-relevant test in
the chain.
Dependencies: #5152 (helper), #5155 (CRD type and runtime config
mirror). Blocks: nothing in the implementation chain — the operator
translation phase consumes the runtime-config field added here, but
the controller-side translation can be built in parallel and merged
in either order.
Acceptance Criteria
OAuth2Config has the runtime mirror of IdentityFromToken,
with Validate() requiring a non-empty subject path when the block
is set.
The token-response rewriter extracts identity from the raw
pre-rewrite body when IdentityFromTokenConfig is configured;
extraction runs before any wire-format rewrite.
ExchangeCodeForIdentity honours the priority chain: identityFromToken first, then userInfo, then synthesis.
When identityFromToken is configured but extraction fails
(e.g. wrong path), the call returns an error wrapping ErrIdentityResolutionFailed. The userinfo HTTP endpoint is NOT
contacted in this case (asserted by a tripwire test).
When identityFromToken is configured and extraction succeeds,
the returned Identity has Synthetic == false so the callback
handler runs UserResolver normally.
The refresh path does not perform identity extraction.
OIDC providers ignore IdentityFromToken if it is somehow set
on their base config, and emit a defence-in-depth WARN.
No part of the response body appears in any returned error or
any logged record at any level.
task lint-fix and task test pass (including a go test -race run) with no regressions in the existing pkg/authserver/upstream/... test suite.
Out of Scope
Operator-side translation from CRD field to runtime config —
separate task.
Updating synthesis-mode-detection helpers (the operator status
condition + provider-construction WARN) to recognise identityFromToken as a real-identity path — separate task.
YAML examples — separate task.
Audit-log changes — the JWT name claim populated from the
extracted Identity.Name flows through the existing audit reader
unchanged.
Wire
identityFromTokeninto the OAuth2 upstream providerDescription
Phase 3 of the Snowflake /
identityFromTokenstory (#5150). Connectthe pure helper (#5152) and the CRD field (#5155) into the embedded
auth server's runtime: extract identity from the raw token-endpoint
response body and use it as the resolved upstream identity, sibling
to the existing
userInfoand PR #5094 synthesis paths.Context
The existing token-response rewriter already reads and parses every
successful token-endpoint response in order to normalise non-standard
envelopes (e.g. GovSlack's nested fields). Extend it to also extract
user identity claims from the same body when an
IdentityFromTokenConfigis configured onOAuth2Config.Identity extraction runs on the RAW pre-rewrite body, so identity
paths are resolved against the original provider response even when
tokenResponseMappingis also configured (Slack v2 needs both: theuser-level access token nested under
authed_user.access_tokenandthe user identifier under
authed_user.id).The new priority chain in
BaseOAuth2Provider.ExchangeCodeForIdentity:IdentityFromToken— when configured, return the extractedidentity. If extraction failed (path didn't resolve), return
ErrIdentityResolutionFailedwithout falling back touserInfoor synthesis — "identityFromToken set" is an explicit operator
declaration that the token response is the identity source, and
silently falling through would mask misconfiguration.
UserInfo— existingfetchUserInfopath, unchanged.synthesizeIdentitypath (PR Allow OAuth2 upstreams to omit userInfo config #5094),unchanged.
The refresh path passes nil for the identity config, because providers
like Snowflake omit
usernameon refresh; identity is captured onceat auth-code time and persisted into session storage by the callback
handler.
OIDC providers always have an ID-token-derived subject and discard
the rewriter's identityFromToken return value. A defence-in-depth
WARN fires if a future config-loader bug ever sets
IdentityFromTokenon an OIDC base config (the field is structurally absent on the OIDC
CRD type today).
A tripwire test asserts the userinfo HTTP endpoint is never contacted
when
identityFromTokenis configured — including on extractionfailure. This is the single most important security-relevant test in
the chain.
Dependencies: #5152 (helper), #5155 (CRD type and runtime config
mirror).
Blocks: nothing in the implementation chain — the operator
translation phase consumes the runtime-config field added here, but
the controller-side translation can be built in parallel and merged
in either order.
Acceptance Criteria
OAuth2Confighas the runtime mirror ofIdentityFromToken,with
Validate()requiring a non-empty subject path when the blockis set.
pre-rewrite body when
IdentityFromTokenConfigis configured;extraction runs before any wire-format rewrite.
ExchangeCodeForIdentityhonours the priority chain:identityFromTokenfirst, thenuserInfo, then synthesis.identityFromTokenis configured but extraction fails(e.g. wrong path), the call returns an error wrapping
ErrIdentityResolutionFailed. The userinfo HTTP endpoint is NOTcontacted in this case (asserted by a tripwire test).
identityFromTokenis configured and extraction succeeds,the returned
IdentityhasSynthetic == falseso the callbackhandler runs UserResolver normally.
IdentityFromTokenif it is somehow seton their base config, and emit a defence-in-depth WARN.
any logged record at any level.
task lint-fixandtask testpass (including ago test -racerun) with no regressions in the existingpkg/authserver/upstream/...test suite.Out of Scope
separate task.
condition + provider-construction WARN) to recognise
identityFromTokenas a real-identity path — separate task.nameclaim populated from theextracted
Identity.Nameflows through the existing audit readerunchanged.