jwt-auth
jwt-auth copied to clipboard
Token invalidate not working
Subject of the issue
JWT token is not invalidated after logout
Your environment:
Q | A |
---|---|
Bug? | yes |
Framework | Laravel |
Framework version | 8.65 |
Package version | 1.2.0 |
PHP version | 8.0.11 |
Steps to reproduce
JWT token is not invalidated after logout using Auth::logout() or Auth::logout(true)
Expected behaviour
JWT should be invalidated after logout and token shouldn't be re-usable.
Just to clarify, tokens by itself are stateless and can't be invalidated.
But yes, "invalidation" is possible by adding them to the "blacklist", thus my questions:
- do you have the blacklist config enabled and is your cache working?
- do you get any errors/exceptions?
Just to clarify, tokens by itself are stateless and can't be invalidated.
But yes, "invalidation" is possible by adding them to the "blacklist", thus my questions:
- do you have the blacklist config enabled and is your cache working?
- do you get any errors/exceptions?
Hi @mfn , yes, blacklist config (JWT_BLACKLIST_ENABLED) always set to true in jwt.php and also my cache is working fine (CACHE_DRIVER=file). And i am not getting any errors.
Besides, i try to downgrade PHP to version 7 and change the jwt package back to tymon package, the token invalidate is working fine after logged out.
Besides, i try to downgrade PHP to version 7 and change the jwt package back to tymon package, the token invalidate is working fine after logged out.
Thanks, that's vital information!
Which specific version of tymon did you try compared to this 1.2.0 ?
In my opinion this is the same as in #44 . Besides JWT_BLACKLIST_ENABLED you need to set JWT_SHOW_BLACKLIST_EXCEPTION to true.
@eschricker sorry, missed this myself
Why does enable the exception for logging purpose "fix" the issue? Wouldn't that be a bug in itself? 🤔
public function decode(Token $token, $checkBlacklist = true)
{
$payloadArray = $this->provider->decode($token->get());
$payload = $this->payloadFactory
->setRefreshFlow($this->refreshFlow)
->customClaims($payloadArray)
->make();
if ($checkBlacklist && $this->blacklistEnabled && $this->blacklist->has($payload)) {
if (
$checkBlacklist &&
$this->blacklistEnabled &&
$this->blacklist->has($payload) &&
$this->getBlackListExceptionEnabled()
) {
throw new TokenBlacklistedException('The token has been blacklisted');
}
}
return $payload;
}
This is the decode function. If JWT_SHOW_BLACKLIST_EXCEPTION
is false (what is the default value), the exception will never be thrown and the payload will always be returned, even if blacklisting is activated and the token is blacklisted.
Besides the unnecessary nested ifs, I don't know if this is a bug. @Messhias contributed the code and can clarify why it is implemented that way. @mfn in my opinion this could be a bug, because JWT_SHOW_BLACKLIST_EXCEPTION
disables the backlisting mechanism. But probably this was desired. The default combination of JWT_BLACKLIST_ENABLED=true
and JWT_SHOW_BLACKLIST_EXCEPTION=false
is misleading.
public function decode(Token $token, $checkBlacklist = true) { $payloadArray = $this->provider->decode($token->get()); $payload = $this->payloadFactory ->setRefreshFlow($this->refreshFlow) ->customClaims($payloadArray) ->make(); if ($checkBlacklist && $this->blacklistEnabled && $this->blacklist->has($payload)) { if ( $checkBlacklist && $this->blacklistEnabled && $this->blacklist->has($payload) && $this->getBlackListExceptionEnabled() ) { throw new TokenBlacklistedException('The token has been blacklisted'); } } return $payload; }
This is the decode function. If
JWT_SHOW_BLACKLIST_EXCEPTION
is false (what is the default value), the exception will never be thrown and the payload will always be returned, even if blacklisting is activated and the token is blacklisted.Besides the unnecessary nested ifs, I don't know if this is a bug. @Messhias contributed the code and can clarify why it is implemented that way. @mfn in my opinion this could be a bug because
JWT_SHOW_BLACKLIST_EXCEPTION
disables the backlisting mechanism. But probably this was desired. The default combination ofJWT_BLACKLIST_ENABLED=true
andJWT_SHOW_BLACKLIST_EXCEPTION=false
is misleading.
@weeliem the token is always blacklisted by since you set up JWT_BLACKLIST_ENABLED to true, just put this is to true in your .env, it's simple (otherwise you found out a bug).
@eschricker JWT_SHOW_BLACKLIST_EXCEPTION I created that because always when we logout the library throws a blacklisted exception without any reason, just log out and trigger an exception and this is was very annoying because was filling out our production log with errors which the end of the day was not, so it's a feature just to hide unnecessary triggers (mainly this on case of logout).
I have tested it on our systems. If JWT_SHOW_BLACKLIST_EXCEPTION
is set to false and I invalidate the token, the token will be blacklisted and but the token can be used further. Due to the bypassing of the exception throwing.
The blacklisting mechanism just works, if JWT_SHOW_BLACKLIST_EXCEPTION
is set to true.
I can't reproduced the behaviour that TokenBlacklistedException('The token has been blacklisted')
is thrown on logout.
The call graph if you logout should be the following:
- JWTGuard::logout
- requiredToken() -> return JWT
- invalidate()
- Manager::invalidate
- throws Exception if blacklisting is not enabled
- Blacklist::addForever or Blacklist::add
- Manager::invalidate
- invalidate()
- requiredToken() -> return JWT
So this should be fine.
From my understanding on every authenticated request the Authenticated-middleware is used. Where the call graph should looks like this:
- Authenticate
- guest --> calls JWTGuard::guest which is implemented in GuardHelpers
- check()
- user()
- JWT::check()
- JWT::checkOrFail()
- JWT::getPayload()
- JWT::decode()
- JWT::checkOrFail()
- JWT::check()
- user()
- check()
- guest --> calls JWTGuard::guest which is implemented in GuardHelpers
The check if a token is blacklisted happens in JWT::decode
. Therefore if JWT_SHOW_BLACKLIST_EXCEPTION
is enabled, no exception will be thrown and the token (which is still valid) is returned.
@Messhias do you probably call an authenticated route after you have logged out with the already blacklisted token and therefore the exception is thrown?
@mfn can you recheck the behaviour if JWT_SHOW_BLACKLIST_EXCEPTION is set to false, that you still can use the blacklisted token?
i have tested with JWT_SHOW_BLACKLIST_EXCEPTION set to true. The invalidating is working fine after JWT_SHOW_BLACKLIST_EXCEPTION set to true in env.
I have tested it on our systems. If
JWT_SHOW_BLACKLIST_EXCEPTION
is set to false and I invalidate the token, the token will be blacklisted and but the token can be used further. Due to the bypassing of the exception throwing. The blacklisting mechanism just works, ifJWT_SHOW_BLACKLIST_EXCEPTION
is set to true.I can't reproduce the behavior that
TokenBlacklistedException('The token has been blacklisted')
is thrown on logout.The call graph, if you log out, should be the following:
JWTGuard::logout
requiredToken() -> return JWT
invalidate()
Manager::invalidate
- throws Exception if blacklisting is not enabled
- Blacklist::addForever or Blacklist::add
So this should be fine.
From my understanding of every authenticated request the Authenticated-middleware is used. Where the call graph should look like this:
Authenticate
guest --> calls JWTGuard::guest which is implemented in GuardHelpers
check()
user()
JWT::check()
JWT::checkOrFail()
- JWT::getPayload()
- JWT::decode()
The check, if a token is blacklisted, happens in
JWT::decode
. Therefore ifJWT_SHOW_BLACKLIST_EXCEPTION
is enabled, no exception will be thrown and the token (which is still valid) is returned.@Messhias do you probably call an authenticated route after you have logged out with the already blacklisted token and therefore the exception is thrown?
@mfn can you recheck the behavior if JWT_SHOW_BLACKLIST_EXCEPTION is set to false, that you still can use the blacklisted token?
Hi @eschricker, no I wasn't calling anything, I tested with a fresh project at that time when I opened the issue in the forked repository and sometimes the exception threw and sometimes not and suddenly start throwing without any reason and this exception it's like the same issue that we have on #30, it's a bad exception to be throw and as you mentioned in the comment https://github.com/PHP-Open-Source-Saver/jwt-auth/issues/30#issuecomment-957228579 that could be just set up to false and populate the log or something like that.
Probably there is another inconsistent exception being thrown on the library that we didn't catch yet.
And as @weeliem told, just set up the env variable to true and it's done, you'll see the exception again. What I have done in the code was just a dynamization of how the exception will be thrown.
Spent the better part of today trying to understand why blacklisted tokens are still authenticated as acceptable, until I read this forum. Should probably have started here. Is it possible to write in big bold letters in the documentation that blacklisted jwt tokens still authenticate users unless JWT_SHOW_BLACKLIST_EXCEPTION is set to true in .env? Everything else is working great so far. Thanks for the great work!
Spent the better part of today trying to understand why blacklisted tokens are still authenticated as acceptable, until I read this forum. Should probably have started here. Is it possible to write in big bold letters in the documentation that blacklisted jwt tokens still authenticate users unless JWT_SHOW_BLACKLIST_EXCEPTION is set to true in .env? Everything else is working great so far. Thanks for the great work!
It is seems to be bug, so no documentation update required 🙃 We probably will move JWT_SHOW_BLACKLIST_EXCEPTION
somewhere else, since it's kinda security problem. Will try to find my time tomorrow
So, I've investigated the issue. @Messhias, am I right, thinking that by #7 your intent was to fix an issue with deprecated jwt.refresh
middleware, when next request with previous token was rejected?
If so, check added by you in #7 will be moved to this middleware only, keeping in mind that is is already deprecated.
So, I've investigated the issue. @Messhias, am I right, thinking that by #7 your intent was to fix an issue with deprecated
jwt.refresh
middleware, when next request with previous token was rejected?If so, check added by you in #7 will be moved to this middleware only, keeping in mind that is is already deprecated.
But for this issue this approach is already solved?
JWT token is not invalidated after logout using Auth::logout() or Auth::logout(true)
I HAVE THE SAME PROBLEM, ANY SOLUTION?
JWT token is not invalidated after logout using Auth::logout() or Auth::logout(true)
I HAVE THE SAME PROBLEM, ANY SOLUTION?
The workaround is mentioned above: Set JWT_SHOW_BLACKLIST_EXCEPTION
in your .env to true
This issue had me cost a hour of debugging. Documentation should be updated.
The invalidating is working fine after I set JWT_SHOW_BLACKLIST_EXCEPTION to true in jwt.php file .
Yeah I also spent a few hours finding that some exception config is disabling blacklist at all and break invalidation.
You can't return a payload if it's blacklisted and it doesn't matter about exception. So this is totally wrong to skip this check and just return a payload for invalidated token!