[Security] Stamp auth_time from the OIDC ID token claim - #66015
Open
nicolas-grekas wants to merge 1 commit into
Open
[Security] Stamp auth_time from the OIDC ID token claim#66015nicolas-grekas wants to merge 1 commit into
nicolas-grekas wants to merge 1 commit into
Conversation
nicolas-grekas
added this pull request to stack #66017
September 11, 2026 13:26
nicolas-grekas
force-pushed
the
is-authenticated-recently-oidc
branch
from
September 13, 2026 07:01
4b7b8fe to
47ee1df
Compare
nicolas-grekas
force-pushed
the
is-authenticated-recently-oidc
branch
from
September 13, 2026 08:42
47ee1df to
1d5db6f
Compare
AuthenticatorManager calls createToken() before it dispatches INTERACTIVE_LOGIN, so AuthenticationTimeListener now only fills the attribute when no authenticator recorded it. OidcLoginAuthenticator carries the "auth_time" claim through the passport and clamps it to the present, so a silent SSO login is not mistaken for a fresh one.
nicolas-grekas
force-pushed
the
is-authenticated-recently-oidc
branch
from
September 13, 2026 08:54
1d5db6f to
e283875
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part 2 of the sudo mode stack, on top of #66014.
OIDC already knows when the user really authenticated: the ID token carries an
auth_timeclaim, andOidcIdTokenvalidates it againstmax_age. Sudo mode was ignoring it, so a silent SSO login from a session the provider opened hours ago counted as a fresh authentication.The ordering trap, which is the actual content of this PR
AuthenticatorManagercallscreateToken()at line 200 and dispatchesINTERACTIVE_LOGINat line 236.OidcLoginAuthenticator::isInteractive()returnstrue, soAuthenticationTimeListeneralready stamps every OIDC login with the current time. Setting the attribute increateToken()and changing nothing else is therefore a silent no-op: the listener overwrites it milliseconds later, and a unit test oncreateToken()alone passes while the feature does nothing.So the listener becomes a default rather than an assignment:
That establishes the contract worth having anyway: an authenticator that knows the real authentication time stamps it, and the listener only fills the gap. It is safe because every token reaching
INTERACTIVE_LOGINis freshly built bycreateToken();SwitchUserTokenneither copies the original token's attributes nor dispatches that event.AuthenticationTimeListenerTest::testItDoesNotOverwriteATimeTheAuthenticatorAlreadyRecorded()pins it. I verified it bites by removing the guard: it fails.Two details that are not optional
$idTokenClaims, not$claims. Under the defaultuser_data_source: userinfo,$claimsis the UserInfo response, which does not carryauth_time.min($authTime, now). The claim is only validated whenmax_ageis requested, so without it a provider whose clock runs ahead would date the stamp in the future and extend the windowIS_AUTHENTICATED_RECENTLYgrants beyond the configured lifetime. Non-numeric values are discarded rather than trusted, sincetime() - '<string>'is a TypeError inside the voter.Fallback
When the provider sends no
auth_time, the attribute is left unset and the listener stamps the login instant, which keeps OIDC consistent with form login, JSON login and login links. The honest caveat, for the docs: if the IdP performed silent SSO and sent no claim, "now" overstates the freshness, and the answer is to configuremax_age, which makes the claim mandatory and validated.A silent token refresh does not bump the stamp:
OidcTokenRefresherwrites only the fouroidc_*attributes and dispatches no event, which matches OIDC Core 12.2, where a refreshed ID token'sauth_timestill reports the original authentication.