[Security][SecurityBundle] Add a re-authentication entry point - #66016
Open
nicolas-grekas wants to merge 1 commit into
Open
[Security][SecurityBundle] Add a re-authentication entry point#66016nicolas-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-entry-point
branch
from
September 13, 2026 07:01
75d78a5 to
2b10277
Compare
Neirda24
reviewed
Sep 13, 2026
| && \array_key_exists(9, ($exceptionListener = $container->getDefinition($exceptionListenerId))->getArguments()) | ||
| && null !== $configuredReAuthEntryPoint = $exceptionListener->getArgument(9) | ||
| ) { | ||
| $exceptionListener->replaceArgument(9, new Reference($entryPoints[$configuredReAuthEntryPoint] ?? $fallbackEntryPoints[$configuredReAuthEntryPoint] ?? $configuredReAuthEntryPoint)); |
Contributor
There was a problem hiding this comment.
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) |
Contributor
There was a problem hiding this comment.
Might be good to allow true and null as a value so it uses the default entry point?
nicolas-grekas
force-pushed
the
is-authenticated-recently-entry-point
branch
from
September 13, 2026 08:42
2b10277 to
147b036
Compare
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
force-pushed
the
is-authenticated-recently-entry-point
branch
from
September 13, 2026 08:54
147b036 to
8fb6804
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 3 of the sudo mode stack, on top of #66015.
A denied
IS_AUTHENTICATED_RECENTLYcurrently 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:Why an entry point and not an access denied handler
handleAccessDeniedException()is strictly ordered: a token that is not full fledged goes tostartAuthentication()and the handler is never consulted; otherwiseaccessDeniedHandler,elseiferrorPage. A handler occupies argument 6, andSecurityExtensionfills argument 5 only in theelseif, so shipping this as a handler would makeaccess_denied_urlunreachable at both the firewall and the application level. The re-authentication decision belongs next to theisFullFledgedbranch that already pre-empts both, so that is where it goes.A new
ReAuthenticationRequiredException extends InsufficientAuthenticationExceptioncarries the intent to the entry point, which is what lets an OIDC entry point sendprompt=loginlater. It deliberately does not extendAccountStatusException, whichstartAuthentication()treats as "clear the token".Matching the attribute exactly, not with in_array()
AccessListenerdecides anaccess_controlrule 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 lacksROLE_ADMINinto 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:
Votecarriesvoter,result,reasonsandextraData, but not the attribute it voted on, andAuthenticatedVoter's denial reason is the same string forIS_AUTHENTICATED_FULLYandIS_AUTHENTICATED_RECENTLY.Failure modes handled
NotAnEntryPointExceptionfalls through to the normal access denied handling, because 403 describes a stale authentication better than the 401 the entry point machinery would otherwise produce.IS_AUTHENTICATED_RECENTLYis always denied and re-authentication would loop forever.Known limitations, for the docs
setTargetPath()skips unsafe methods, so a deniedPOSTsaves no return URL. Guard the GET that renders the confirmation form, not the POST.allow_if: "is_recently_authenticated()") leaves anExpressioningetAttributes()and does not trigger the branch.auth_timeat all, sinceSwitchUserTokendoes not copy attributes andSWITCH_USERis a different event.