Thanks to visit codestin.com
Credit goes to github.com

Skip to content

[Security][SecurityBundle] Add IS_AUTHENTICATED_RECENTLY to require a fresh authentication - #66001

Closed
nicolas-grekas wants to merge 1 commit into
symfony:8.2from
nicolas-grekas:feature/is-authenticated-recently
Closed

[Security][SecurityBundle] Add IS_AUTHENTICATED_RECENTLY to require a fresh authentication#66001
nicolas-grekas wants to merge 1 commit into
symfony:8.2from
nicolas-grekas:feature/is-authenticated-recently

Conversation

@nicolas-grekas

@nicolas-grekas nicolas-grekas commented Sep 11, 2026

Copy link
Copy Markdown
Member
Q A
Branch? 8.2
Bug fix? no
New feature? yes
Deprecations? no
Issues Fix #33955
License MIT

Sudo mode, asked for in #33955 seven years ago. The demand was never in question (@fabpot in 2022: "there are no questions about 'do we want it?'. It's more like, 'who wants to work on it?'"), but the issue was parked behind the broader "identity trust level" model in #39308, and both that issue and #30914 have since been closed without it. So this takes the self-contained route.

What it does

IS_AUTHENTICATED_FULLY describes how the session began, never how recently the user proved possession of their credentials. This adds a second axis:

#[IsGranted('IS_AUTHENTICATED_RECENTLY')]
public function deleteAccount(): Response
security:
    recent_authentication_lifetime: 900 # seconds, the default

Three pieces:

  • AuthenticationTimeListener records the moment of an interactive authentication as the auth_time token attribute. auth_time is the name OIDC Core already uses for this, which keeps the door open for the ID token claim to feed the same stamp later.
  • AuthenticatedVoter grants IS_AUTHENTICATED_RECENTLY while that stamp is within the configured lifetime.
  • is_recently_authenticated() in the expression language and IsGrantedContext::isAuthenticatedRecently() for the closure form, matching the existing is_fully_authenticated() pair.

Design notes

  • No BC break and no new storage. AbstractToken::__serialize() already includes $attributes, and ContextListener::refreshUser() mutates the token in place rather than rebuilding it, so the stamp survives the session round trip on its own. A functional test asserts exactly that rather than trusting it.
  • Deliberately outside the granted hierarchy. IS_AUTHENTICATED_RECENTLY is strictly stronger than IS_AUTHENTICATED_FULLY, so being fully authenticated does not grant it. A token with no auth_time is denied, which is the safe default for non-interactive authenticators.
  • Remember-me can never satisfy it. RememberMeAuthenticator::isInteractive() returns true, so a remember-me login does get stamped; the isFullFledged() guard is what keeps it denied. That is covered by a test, since it is the one place the feature could silently go wrong.
  • This answers @stof's objection on the issue, that a shorter session lifetime might be enough. It is not: shortening the session changes when the session dies, not how recently the user authenticated within it.
  • Clock is an optional injected service. Both the voter and the listener take a nullable Psr\Clock\ClockInterface, wired as service('clock')->nullOnInvalid(), which is the idiom FrameworkBundle itself uses at Resources/config/services.php:110 and web.php:66. clock is defined unconditionally by the DI kernel and aliased for both ClockInterface flavours, so a real application always gets it; the time() fallback only covers standalone use of the components, so neither package needs a new dependency. A functional test asserts the clock actually arrives, since nullOnInvalid() would otherwise let a wrong service id fall back to time() unnoticed.

Deliberately not in scope

  • An entry point that turns a denial into a redirect to a re-authentication form, mirroring access_denied_handler. Worth doing, but it is a separate surface and this is already reviewable on its own.
  • Feeding the OIDC auth_time claim into the same stamp, so max_age and sudo mode agree.

Docs to follow once the shape is agreed.

Thanks @javiereguiluz for the original RFC, and @stof and @wouterj for the constraints in the thread that shaped this.

… fresh authentication

Records the time of the last interactive authentication as the `auth_time` token
attribute, and grants IS_AUTHENTICATED_RECENTLY while it is within the configured
lifetime. This is what lets a sensitive action ask the user to prove possession of
their credentials again instead of trusting a session opened long ago.
@nicolas-grekas
nicolas-grekas force-pushed the feature/is-authenticated-recently branch from 56462ac to 146ca77 Compare September 11, 2026 12:55
@nicolas-grekas

Copy link
Copy Markdown
Member Author

Reopened from a branch on symfony/symfony so it can carry a stack: #66014 is this change, #66015 feeds the OIDC auth_time claim into the same stamp, and #66016 adds the re-authentication entry point. Stack #66017.

@nicolas-grekas
nicolas-grekas deleted the feature/is-authenticated-recently branch September 11, 2026 13:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Security] Add "sudo mode"

2 participants