-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
Redirect authenticated with RememberMeToken user to login form after access check in controller #16026
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
@wiistriker my guess is that both anonymous and rememberme are considered to require an additional login if permissions are missing, elevating their permission level. |
@iltar In my case user doesnt have rights to admin $company even if he fill username and password again. After he fill form, he get proper 403 page. It's very strange behaviour. |
Code which represent flow which i want
If user is logged in he get 403 error page and will be redirected to login form if user is anonymous. Method $this->denyAccessUnlessGranted('admin', $company); become really useless |
I've been getting frustrated with this too. In Symfony/Component/Security/Http/Firewall/ExceptionListener.php it checks for fullyFledged and then begins the authentication process. The default AuthenticationTrustResolver will always return false if the user is only "remembered".
|
Same here. But don't know how to fix. How to decide when redirection is correct, and when not. |
This strange behaviour is still up to date in Symfony4.
or :
|
Same for me here, I use Symfony 4.2. I even tried to handle the denyAccessException but it didn't work for me. |
<- new to Symfony Stumbled across the same issue as OP. My solution was writing a custom ExceptionListener. app/config/services.yaml: app/src/security/CustomExceptionListener.php
If you don't want to inject the container, you can alias the AuthenticationTrustResolver and inject it into the class constructor. app/config/services.yaml: This way, the 'isGranted' and 'denyAccessUnlessGranted' functionality is available again and does not redirect to the login page. |
You can just create your own AuthenticationTrustResolver and decide yourself which are the conditions that are to be considered if a user isFullFledged or not, so the user will just get the access denied page instead of a redirect. I do have the feeling that this feature assumes a lot of things about the workflows that people use. Maybe I didn't read or find the documentation for this, but we were throwing AccessDenied exceptions (or bundles that we use) for the purpose of showing an access denied page and be done with it (we upgraded from an older version of Symfony), but now the framework takes over this and redirects the user to a login page. LaterEdit: I worked on way too old versions on Symfony up until recently. The documentation actually is quite clear on what you can do: https://symfony.com/doc/4.4/security/access_denied_handler.html |
Thank you for this issue. |
Friendly ping? Should this still be open? I will close if I don't hear anything. |
I can't confirm. I had reported my issue 5 years ago and ended up resolving it by extending the AuthenticationTrustResolver and providing the desired functionality within my application code, essentially the solution that @daniel-iwaniec suggested. That particular application is no longer under active development so I'm unsure how newer versions of Symfony may have affected that solution. |
@aromer I can confirm, it's still an issue sadly. |
Thank you for this issue. |
Just a quick reminder to make a comment on this. If I don't hear anything I'll close this. |
|
I see mostly problems in this issue, but no proposed solutions on how to "fix" this issue in Symfony. The only way I can see this issue move forward and no longer get these stalled questions is if people proposed solutions, maybe even as a pull request. Note that a rememberme cookie is considered to be a weak authentication mechanism. As such, it is not considered "full fledged". That logic still makes sense to me. |
I didn't work on the project that showed this behaviour for over a year (symfony 4.x), but I tried to replicate it in my current project (symfony 5.3). @danieldenbraven is right: the solution can be found in the documentation Not setting a custom response and stopping event propagation in case of a remember-me token redirects to the desired 'Access denied' page. The exception is no longer 'overwritten' by a any further exceptions. (E.g. a 401 response with a redirect to the login page.)
Whether this opens up any security issues I'm not sure... access to any other 'secured' pages the user ought to have access to (e.g. to a 'my profile' page) is granted as if he/she was authorized via a regular login. |
Same issue here on 5.3. There is solution using
In my case, I don't use
|
Thank you for this issue. |
Just a quick reminder to make a comment on this. If I don't hear anything I'll close this. |
This is still present and needs to be resolved. Symfony assumes too much when changing an AccessDeniedException into an InsufficientAuthenticationException. There is no basis to do so. If the developer expects the user to be fully authenticated, they can query for IS_AUTHENTICATED_FULLY themselves. |
@wouterj in regards to your query for a solution, I would suggest the following:
That way developers have all the control they need and an example in the code if they wish to programmatically kick the user to the authentication start. |
@uncaught just for my understanding: |
Yes and no. The If you think it cleaner, we could make An alternative very simple fix would be to explicitly check the roles before jumping to conclusions: src/Symfony/Component/Security/Http/Firewall/ExceptionListener.php private function handleAccessDeniedException(GetResponseForExceptionEvent $event, AccessDeniedException $exception)
{
$event->setThrowable(new AccessDeniedHttpException($exception->getMessage(), $exception));
$token = $this->tokenStorage->getToken();
- if (!$this->authenticationTrustResolver->isFullFledged($token)) {
+ if (in_array(IS_AUTHENTICATED_FULLY, $exception->getAttributes()) && !$this->authenticationTrustResolver->isFullFledged($token)) { |
@uncaught tyvm for your response! :)
Which makes no sense to me: assuming the user tries to access a page he/she wouldn't have access to anyway isn't mitigated by a redirect to the login page (only to get a "Access denied 403" response immediately after logging in). This minor change in src/Symfony/Component/Security/Http/Firewall/ExceptionListener.php has the desired effect: - if (!$this->authenticationTrustResolver->isFullFledged($token)) {
+ if (!$this->authenticationTrustResolver->isFullFledged($token) && !$this->authenticationTrustResolver->isRememberMe($token)) { With a major drawback: the possibilty for a user to upgarde his/her token from remember_me to full-fledged is removed completely. To avoid this from happening, I wrote a custom AccessDeniedListener when I ran into this issue a few years back (Symfony 5.3.3):
Since there are always 2 exceptions queued up when access is denied (the initial AccessDeniedException and the AccessDeniedHttpException from method handleAuthenticationException), stopping the event propagation forces the framework to only handle the first one i.e. the user gets to see the "Access denied 403" page and is not redirected, regardless of the whether full-fledged or remembered. An anonymous user still gets redirected to the login page. TL;DR Though I may very well be wrong about what your simple fix does, I nethertheless prefer your initial proposal of essentially splitting the exception in two i.e. not converting one exception into another:
Your initial proposal - though a little bit more 'verbous' and not a 'simple fix' - I'd consider to be very 'clean' and the best solution:
|
Offering to get a full-fledged token before getting an error is a feature we have since Symfony 2.0, because it is totally possible for any voter to include a check on |
@stof what is your point? It's not an offer, it's a forced redirect to a 401 response (or login page). I find that very inconvenient when I already know that changing the authentication method will not change the end result. @chris-k-k let me explain what the simple fix would do: if (in_array(IS_AUTHENTICATED_FULLY, $exception->getAttributes()) && !$this->authenticationTrustResolver->isFullFledged($token)) { This would mean, that only if you threw an I found this very simple because So lets say you are logged in via remember me:
|
@uncaught ty for the explanation! :) I'd still prefer the separation of authorization and authentication from your initial proposal:
This still applies, no? Given that 'ROLE_NAME' is the authorization (or 'IS_ALLOWED_TO_EDIT' or w/e) and 'IS_AUTHENTICATED_FULLY' would be the authentication.
EDIT: Just found out that the support for an array of attributes has been removed a long, long time ago ;) #33584 |
btw i am still using simple code:
|
Pretty inconvenient if this needs to be repeated in every controller. After trying to implement @uncaught's solution (which unfortunately didn't work due to isGranted() & denyAccessUnlessGranted() not allowing an array of attributes), this is what I did to get the desired result (in an old project - Symfony 5.3.3): // Symfony\Component\Security\Http\Firewall\ExceptionListener
- if (!$this->authenticationTrustResolver->isFullFledged($token)) { ... }
+ if (null === $this->tokenStorage->getToken() || in_array('IS_AUTHENTICATED_FULLY', $exception->getAttributes())) { ... } If there is no token set (i.e. the user is not authenticted at all) or the exception attribute is set to 'IS_AUTHENTICATED_FULLY', the authentication process will start (e.g. redirecting to the login page). Else, a simple access denied exception is thrown. I'm now able to explicitly secure resources (with controller attributes for example):
I can customize the behaviour of the authentication process by overriding the start() method of class Symfony\Component\Security\Http\Authenticator\AbstractLoginFormAuthenticator (documentation):
Here, it would be cleaner to be able to differentiate between a no authentication exception and an insufficient authentication exception (as per uncaught's initial proposal). It would make it possible to check which kind of exception was thrown instead of examining the token. But regardless, it works... |
With the changes the separation will at least be a bit more clear. If you wanted to split hairs, the whole idea of
Oh, I didn't know that was dropped with Symfony 5. I'm still on 4, but never used arrays anyway, so I guess I've never seen the deprecation notices about it. Weird that the AccessDescisionManager keeps using arrays though. And now I see that with Symfony 6 the AuthorizationChecker no longer authenticates the user if they are not authenticated. So I suppose I am a bit outdated. I don't know at which point the user gets authenticated anymore. Is that code in the ExceptionListener the only thing left that starts the authentication? Then your suggestion to check for a Although the fact that they left the attributes in there as array makes me a bit nervous because your code assumes that the IS_AUTHENTICATED_FULLY was in fact the attribute that failed. So I'd still leave the // Symfony\Component\Security\Http\Firewall\ExceptionListener
- if (!$this->authenticationTrustResolver->isFullFledged($token)) { ... }
+ if (null === $this->tokenStorage->getToken() || in_array('IS_AUTHENTICATED_FULLY', $exception->getAttributes()) && !$this->authenticationTrustResolver->isFullFledged($token)) { ... } Or I might be paranoid. |
Well, your authorization layer is perfectly legitimate to grant or reject a permission based on the level of trust of the authentication. And my answer was about logic that you might implement in custom voters. |
Thank you for this issue. |
We've just upgraded to 5.4 and the behavior is still an issue. Overriding the AuthenticationTrustResolver is not an option because we want to use those trust levels in the future. Luckily we have a custom authenticator with an entry point now, so this is my new 5.4 work around, basically what @chris-k-k suggested: public function start(Request $request, AuthenticationException $authException = null): Response {
if ($authException instanceof InsufficientAuthenticationException && $authException->getToken()?->getUser()) {
return new Response('Access denied', 403);
}
return new Response('Not logged in', 401);
} |
Thank you for this issue. |
Just a quick reminder to make a comment on this. If I don't hear anything I'll close this. |
Hey, I didn't hear anything so I'm going to close it. Feel free to comment if this is still relevant, I can always reopen! |
Hi all, I am struggling with this at the moment and I think this issue deserves to be re-opened. Here's how a typical project goes for me:
I expect my site to just work as normal after adding "remember me". Except it doesn't. Because everywhere that I have a route protected with a custom voter and Now this is not some bizarre edge case. This is a typical project with typical security. But the behaviour I get is not what I expect, and I think that it's not what most developers expect. So now I'm forced to write a custom event handler to get the kind of behaviour that I think is "normal" and should come out of the box. Look, I get why it was written the way it was written. The code that checks access can't make assumptions about the level of trust required to make a decision. Sure. But you also need to recognise that the way it is implemented at the moment produces behaviour that is not what most developers and users would expect. The solution? I don't know... The problem seems to be that we have one entry point for access checks, but two different outcomes (redirect vs forbidden). Having one entry point is great and convenient, but when the developer doesn't have control over the outcome, it breaks down. Actually, it's not that there are two different outcomes, it's that the outcomes are determined by the state of the user, instead of by the check itself. If I'm checking for End of the day, I want to have a way to say "if this check fails, I want to return a 403 Forbidden, even if the user is authenticated via remember me", as well as "if this check fails, then redirect to login". Maybe add a method called |
I agree 100%. This wouldn't be a problem if the authorization system wasn't abused to check for authentication states in the first place. I would remove those IS_AUTHENTICATED* from there, but that would probably be too much of change. As a compromise, the system should only assume a 401 response is the right answer if one of the authentication attributes was checked in the authorization system. |
I have custom voter for check access to admin of Company entity
If AccessDeniedException is thrown i get redirected to login page even if user is logged in (with RememberMeToken). Is this expected behaviour?
I want get 403 error page for authenticated user and redirect to login page for anonymous user. What is right way to do this? Throw AccessDeniedHttpException instead?
https://github.com/symfony/symfony/blob/2.8/src/Symfony/Component/Security/Http/Firewall/ExceptionListener.php#L122 this part of code check that user should be not "anonymous" and not "remember me". I am not sure why we need remember me check here?
The text was updated successfully, but these errors were encountered: