jwt-auth icon indicating copy to clipboard operation
jwt-auth copied to clipboard

Token invalidate not working

Open weeliem opened this issue 3 years ago • 19 comments

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.

weeliem avatar Dec 06 '21 13:12 weeliem

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?

mfn avatar Dec 06 '21 13:12 mfn

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.

weeliem avatar Dec 07 '21 00:12 weeliem

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 ?

mfn avatar Dec 07 '21 08:12 mfn

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 avatar Dec 07 '21 08:12 eschricker

@eschricker sorry, missed this myself

Why does enable the exception for logging purpose "fix" the issue? Wouldn't that be a bug in itself? 🤔

mfn avatar Dec 07 '21 08:12 mfn

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.

eschricker avatar Dec 07 '21 08:12 eschricker

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.

@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).

Messhias avatar Dec 07 '21 09:12 Messhias

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

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()

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?

eschricker avatar Dec 07 '21 10:12 eschricker

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.

weeliem avatar Dec 07 '21 10:12 weeliem

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 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 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 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.

Messhias avatar Dec 07 '21 10:12 Messhias

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!

Hmerman6006 avatar Dec 13 '21 19:12 Hmerman6006

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

leon0399 avatar Dec 13 '21 20:12 leon0399

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.

leon0399 avatar Dec 21 '21 19:12 leon0399

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?

Messhias avatar Jan 18 '22 17:01 Messhias

JWT token is not invalidated after logout using Auth::logout() or Auth::logout(true)

I HAVE THE SAME PROBLEM, ANY SOLUTION?

wpsouto avatar Jan 23 '22 23:01 wpsouto

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

eschricker avatar Jan 24 '22 07:01 eschricker

This issue had me cost a hour of debugging. Documentation should be updated.

SRaromicon avatar Feb 22 '22 14:02 SRaromicon

The invalidating is working fine after I set JWT_SHOW_BLACKLIST_EXCEPTION to true in jwt.php file .

KhalidMh avatar Apr 22 '22 00:04 KhalidMh

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!

aprokopenko avatar May 13 '22 13:05 aprokopenko