Skip to content

[Guard] Failed authentication attempt when authenticated, de-authenticates #25992

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

Closed
linaori opened this issue Jan 31, 2018 · 12 comments
Closed

Comments

@linaori
Copy link
Contributor

linaori commented Jan 31, 2018

Q A
Bug report? yes
Feature request? no
BC Break report? no
RFC? no
Symfony version 2.8+

My scenario:

  • I have a firewall
  • Which has 3 authenticators
  • Entry for one of the authenticators: /hash/{hash}/
  • Authenticates on the main firewall
  • A voter restricts access for this authentication (same firewall context)
  • When access denied by that voter, an in-page login screen will ask for the password to "elevate permissions"
  • Successful authentication will grant you normal permissions, difference is that username is not required

The issue I have now that I'm using guard instead of Simple*, is that every failed authentication attempt, triggers a de-authentication. That means that instead of staying logged in and showing the error message that the password was wrong, the user is logged out and has to fill in both password and username.

This is really simple to reproduce, just try to login with invalid credentials when logged in, and it will de-authenticate you.

For this particular case, it can be solved by removing the following lines: https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Security/Guard/GuardAuthenticatorHandler.php#L100-L103

I think this is a bug, as it's really weird behavior to de-authenticate a user this way. I can provide a PR to fix this in 2.8, otherwise it has to be fixed with a deprecation in 4.1 (which would take too long for my issue though).

2317443

@weaverryan
Copy link
Member

To repeat this:

  1. Authenticate with Guard
  2. While authenticated, try to login again, but fail (e.g. put in the wrong password). You become de-authenticated.

The cause is this code:

public function handleAuthenticationFailure(AuthenticationException $authenticationException, Request $request, AuthenticatorInterface $guardAuthenticator, string $providerKey): ?Response
{
$token = $this->tokenStorage->getToken();
if ($token instanceof PostAuthenticationGuardToken && $providerKey === $token->getProviderKey()) {
$this->tokenStorage->setToken(null);
}

This was put in very purposely by me. But actually, I was just following the lead from AbstractAuthenticationListener:

if ($token instanceof UsernamePasswordToken && $this->providerKey === $token->getProviderKey()) {
$this->tokenStorage->setToken(null);
}

In reality, I can't think of any reason why the token should be set to null when you fail authentication. If you're still authenticated via some other mechanism (e.g. a session cookie), then you should still be authenticated. The original $this->tokenStorage->setToken(null); code was actually added during the original creation of the security component - sha: f216f31. My theory is that this was an oversight/bug way back then... but nobody really caught it. Then, I copied it for Guard.

I think we should fix this. But, it's a behavior change: technically, someone could for some crazy reason, be relying on the fact that a failed login attempt would cause the user to be logged out. Do others agree? Would changing this behavior be a BC break? If not, the fix is SUPER easy. Otherwise, we'll need to add some flag for the new behavior, and trigger a deprecation warning if the flag is not set.

@linaori
Copy link
Contributor Author

linaori commented Jan 31, 2018

My guess is that in a lot of cases, the token would've been NULL (never set in the exception), and thus never triggered this, but that's the best I could come up with.

@curry684
Copy link
Contributor

curry684 commented Feb 1, 2018

I can't think of any reason why the token should be set to null when you fail authentication

Doesn't this happen when elevating by requiring IS_AUTHENTICATED_FULLY in a IS_AUTHENTICATED_REMEMBERED setting? I would certainly expect those cases to cause logout if failed, as it could indicate a session hijack.

@weaverryan
Copy link
Member

Doesn't this happen when elevating by requiring IS_AUTHENTICATED_FULLY in a IS_AUTHENTICATED_REMEMBERED setting? I would certainly expect those cases to cause logout if failed, as it could indicate a session hijack.

Hmm. Well, your session would be hijacked in that case, it would be the remember me cookie that was hijacked. But, fair statement. Though, I'm not convinced that you would want to be logged out anyways. Without the password, I still couldn't access any IS_AUTHENTICATED_FULLY areas, I would remain only IS_AUTHENTICATED_REMEMBERED. It really comes down to the question: if I'm authenticated, should entering my password incorrectly render me not authenticated suddenly? If the answer is yes, then the feature is created correctly.

@curry684
Copy link
Contributor

curry684 commented Feb 1, 2018

I'm not sure whether there's any golden rule to this, but I would myself implement this as it is done now, assuming that the most recent authentication is always leading. If you can't confirm your identity now - don't trust the previous confirmation either.

Perhaps we should have a bit of research on how big common sites solve this?

@linaori
Copy link
Contributor Author

linaori commented Feb 1, 2018

@curry684 but your identity is confirmed, hence the PostAuthenticationGuardToken.

Also note that in my previous setup with Simple* authenticators, this worked just fine

@curry684
Copy link
Contributor

curry684 commented Feb 1, 2018

Yes I know that the behavior is inconsistent, the question is now whether the Simple* authenticators are 'wrong' or the Guard.

I see it like this: when I go the airport I have to show my passport multiple times. The first time I use it to authenticate myself I am given a boarding pass (a 'token') which implicitly confirms my identity. When boarding the plane my passport is checked again, and if it is rejected my boarding pass becomes untrusted automatically. Authentication is, imho, only ever as valid as your most recent attempt.

@linaori
Copy link
Contributor Author

linaori commented Feb 1, 2018

Authentication is, imho, only ever as valid as your most recent attempt.

I disagree with that statement. I've already confirmed that the user is authenticated. In this scenario, a typo in the password would throw away the previous authentication, rather than leaving it at the current level. The previous authentication was correct, only the new one is incorrect.

Your scenario also seems incorrect. You first identify with a passport, but if your boarding pass is rejected, that doesn't automatically invalidate your passport.

@curry684
Copy link
Contributor

curry684 commented Feb 1, 2018

In this scenario, a typo in the password would throw away the previous authentication, rather than leaving it at the current level.

Correct, and intentional on my part.

You first identify with a passport, but if your boarding pass is rejected, that doesn't automatically invalidate your passport.

It's the other way around - if my passport is rejected, it invalidates my boarding pass. That makes sense as it casts doubts on how I got the boarding pass in the first place.

I don't think the two of us should be arguing here, I see your point and I'm not even really opposed to it. I would just do it like it's done currently myself as I'm in the strictness-above-all camp where security is concerned. I think it's up to others to decide "the Symfony way", and based on that we should either fix this one or the Simple authenticators.

@linaori
Copy link
Contributor Author

linaori commented Feb 1, 2018

It's the other way around - if my passport is rejected, it invalidates my boarding pass. That makes sense as it casts doubts on how I got the boarding pass in the first place.

Except that if your passport was already checked and confirmed, it should yield the same result next check, otherwise the first check can't be trusted. My first check can be trusted, thus I expect my authenticator to work like this.

Security and usability often conflict. The better the security, the less user friendly a system becomes. However, in this scenario it doesn't increase or decrease security, it only hinders usability.

@curry684
Copy link
Contributor

curry684 commented Feb 1, 2018

What you are experiencing seems more like an unfortunate side effect of code that is in itself suitably strict. The scenario you're describing isn't "your average web site with a customer login" anymore 😉

@xabbuh
Copy link
Member

xabbuh commented Feb 5, 2018

Let's take a look at a real example from big websites: If you are trying to perform a checkout at Amazon, you will have to reauthenticate. If you fail to do so (maybe because of a typo), you won't be logged out immediately, but you can try to login a again. In my opinion that's the expected behaviour. Just because you made a typo when typing your password shouldn't mean that the whole session should be destroyed.

fabpot added a commit that referenced this issue Apr 22, 2018
…rd (iltar)

This PR was merged into the 2.8 branch.

Discussion
----------

[Security] Fixed being logged out on failed attempt in guard

| Q             | A
| ------------- | ---
| Branch?       | 2.8
| Bug fix?      | yes
| New feature?  | no
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | #25992
| License       | MIT
| Doc PR        | ~

This fixes the issue described in the ticket. After this fix, guard will no longer "forget" your authentication when your next attempt fails.

Commits
-------

4fc0ecb Fixed being logged out on failed attempt in guard
@fabpot fabpot closed this as completed Apr 22, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

7 participants