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

Skip to content

[Security][SecurityBundle] Add a re-authentication entry point - #66016

Open
nicolas-grekas wants to merge 1 commit into
is-authenticated-recently-oidcfrom
is-authenticated-recently-entry-point
Open

[Security][SecurityBundle] Add a re-authentication entry point#66016
nicolas-grekas wants to merge 1 commit into
is-authenticated-recently-oidcfrom
is-authenticated-recently-entry-point

Conversation

@nicolas-grekas

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

Part 3 of the sudo mode stack, on top of #66015.

A denied IS_AUTHENTICATED_RECENTLY currently produces a bare 403, which leaves the user no way back in. This adds a firewall option that starts a fresh authentication instead, and saves the target path so they return where they were:

security:
    firewalls:
        main:
            re_authentication_entry_point: form_login # or a service id

Why an entry point and not an access denied handler

handleAccessDeniedException() is strictly ordered: a token that is not full fledged goes to startAuthentication() and the handler is never consulted; otherwise accessDeniedHandler, elseif errorPage. A handler occupies argument 6, and SecurityExtension fills argument 5 only in the elseif, so shipping this as a handler would make access_denied_url unreachable at both the firewall and the application level. The re-authentication decision belongs next to the isFullFledged branch that already pre-empts both, so that is where it goes.

A new ReAuthenticationRequiredException extends InsufficientAuthenticationException carries the intent to the entry point, which is what lets an OIDC entry point send prompt=login later. It deliberately does not extend AccountStatusException, which startAuthentication() treats as "clear the token".

Matching the attribute exactly, not with in_array()

[AuthenticatedVoter::IS_AUTHENTICATED_RECENTLY] === $exception->getAttributes()

AccessListener decides an access_control rule on all of its roles at once, so a denial of [ROLE_ADMIN, IS_AUTHENTICATED_RECENTLY] does not say which attribute failed. Searching the list would send a user who merely lacks ROLE_ADMIN into a re-authentication that cannot help them, and on a firewall whose login page redirects authenticated users away, into a loop. Pinned by a test.

The reason has to come from the attributes rather than the votes: Vote carries voter, result, reasons and extraData, but not the attribute it voted on, and AuthenticatedVoter's denial reason is the same string for IS_AUTHENTICATED_FULLY and IS_AUTHENTICATED_RECENTLY.

Failure modes handled

  • An entry point that throws NotAnEntryPointException falls through to the normal access denied handling, because 403 describes a stale authentication better than the 401 the entry point machinery would otherwise produce.
  • The option is refused on a stateless firewall at compile time: there is no session to record when the user authenticated, so IS_AUTHENTICATED_RECENTLY is always denied and re-authentication would loop forever.

Known limitations, for the docs

  • setTargetPath() skips unsafe methods, so a denied POST saves no return URL. Guard the GET that renders the confirmation form, not the POST.
  • The expression form (allow_if: "is_recently_authenticated()") leaves an Expression in getAttributes() and does not trigger the branch.
  • An impersonated session has no auth_time at all, since SwitchUserToken does not copy attributes and SWITCH_USER is a different event.

&& \array_key_exists(9, ($exceptionListener = $container->getDefinition($exceptionListenerId))->getArguments())
&& null !== $configuredReAuthEntryPoint = $exceptionListener->getArgument(9)
) {
$exceptionListener->replaceArgument(9, new Reference($entryPoints[$configuredReAuthEntryPoint] ?? $fallbackEntryPoints[$configuredReAuthEntryPoint] ?? $configuredReAuthEntryPoint));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it ok if the entry point is not part of the available authenticators on this firewall ? It seems to silence that we misconfigured it.


// an explicit option only, never inferred: re-authentication is not something
// to start by accident on a firewall that happens to have a single entry point
if ($container->hasDefinition($exceptionListenerId = 'security.exception_listener.'.$firewallName)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be good to allow true and null as a value so it uses the default entry point?

A denied IS_AUTHENTICATED_RECENTLY answered with a bare 403 leaves the user no way
back in. The new "re_authentication_entry_point" firewall option starts a fresh
authentication instead, saving the target path so the user returns where they were.

The branch matches the whole attribute list rather than searching it: an access_control
rule is decided on all of its roles at once, so a denial that also names a missing role
would otherwise send the user into a re-authentication that cannot help. An entry point
that declines to start falls back to the access-denied handling, since 403 describes a
stale authentication better than the 401 an unusable entry point would produce.
@nicolas-grekas
nicolas-grekas force-pushed the is-authenticated-recently-entry-point branch from 147b036 to 8fb6804 Compare September 13, 2026 08:54
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.

3 participants