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

Token immediately blacklisted

Open njt1982 opened this issue 7 years ago • 23 comments

I'm upgrading my Laravel 5.2 app to Lumen 5.4 and have managed to repair my app to a point where I can at least log in again ;)

So on login, I get a token back and my Angular app stores it and makes 3 API requests with it. These all work perfectly.

Then I click something to make another request. This request fails with:

The token has been blacklisted

I can confirm the correct header is being sent:

Authorization:Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9............

I get 2 stack traces... 1/2 is this:

TokenBlacklistedException in Manager.php line 97:
The token has been blacklisted

in Manager.php line 97
at Manager->decode(object(Token)) in JWT.php line 183
at JWT->getPayload() in JWTAuth.php line 60
at JWTAuth->authenticate() in BaseMiddleware.php line 69
at BaseMiddleware->authenticate(object(Request)) in Authenticate.php line 30
at Authenticate->handle(object(Request), object(Closure)) in Pipeline.php line 148
at Pipeline->Illuminate\Pipeline\{closure}(object(Request)) in Pipeline.php line 102
at Pipeline->then(object(Closure)) in RoutesRequests.php line 779
at Application->sendThroughPipeline(array('Tymon\\JWTAuth\\Http\\Middleware\\Authenticate', 'Tymon\\JWTAuth\\Http\\Middleware\\RefreshToken'), object(Closure)) in RoutesRequests.php line 625
at Application->handleFoundRoute(array(true, array('uses' => 'App\\Http\\Controllers\\AuctionController@index', 'middleware' => array('jwt.auth', 'jwt.refresh')), array())) in RoutesRequests.php line 528
at Application->Laravel\Lumen\Concerns\{closure}() in RoutesRequests.php line 782
at Application->sendThroughPipeline(array(), object(Closure)) in RoutesRequests.php line 534
at Application->dispatch(null) in RoutesRequests.php line 475
at Application->run() in index.php line 35

Then 2/2 is this:

UnauthorizedHttpException in BaseMiddleware.php line 71:
The token has been blacklisted
in BaseMiddleware.php line 71
at BaseMiddleware->authenticate(object(Request)) in Authenticate.php line 30
at Authenticate->handle(object(Request), object(Closure)) in Pipeline.php line 148
at Pipeline->Illuminate\Pipeline\{closure}(object(Request)) in Pipeline.php line 102
at Pipeline->then(object(Closure)) in RoutesRequests.php line 779
at Application->sendThroughPipeline(array('Tymon\\JWTAuth\\Http\\Middleware\\Authenticate', 'Tymon\\JWTAuth\\Http\\Middleware\\RefreshToken'), object(Closure)) in RoutesRequests.php line 625
at Application->handleFoundRoute(array(true, array('uses' => 'App\\Http\\Controllers\\AuctionController@index', 'middleware' => array('jwt.auth', 'jwt.refresh')), array())) in RoutesRequests.php line 528
at Application->Laravel\Lumen\Concerns\{closure}() in RoutesRequests.php line 782
at Application->sendThroughPipeline(array(), object(Closure)) in RoutesRequests.php line 534
at Application->dispatch(null) in RoutesRequests.php line 475
at Application->run() in index.php line 35

As you can see in there, I am using jwt.auth and jwt.refresh middleware...

$app->group(['prefix' => 'api/v1'], function() use ($app) {
  $app->post('login', 'AuthController@login');

  $app->group(['middleware' => ['jwt.auth', 'jwt.refresh']], function() use ($app) {
  ...
  ...

Any tips?

njt1982 avatar Jan 29 '17 00:01 njt1982

Note: I can make the error go away by adding this to my .env:

JWT_BLACKLIST_ENABLED=false

But this does not feel like a "fix"... This feels like I have just made the problem go away without actually solving the cause ;)

njt1982 avatar Jan 29 '17 00:01 njt1982

Hi @njt1982 , I am no expert, but I was just poking around in the code in this area. And the code that does the refresh (Manager->refresh) will blacklist the token immediately if you have that enabled, making the tokens "one use only".

        if ($this->blacklistEnabled) {
            // invalidate old token
            $this->invalidate($token, $forceForever);
        }

The refresh middleware seems to only be something you would use if you were doing one use tokens.

Again, I'm no expert, currently poking around the code trying to figure out the right way to add refresh to my project.

Mando-Chris avatar Feb 10 '17 21:02 Mando-Chris

i have this issue too

hsklia avatar Feb 12 '17 09:02 hsklia

@njt1982 Are you storing the refreshed token?

Mando-Chris avatar Feb 12 '17 09:02 Mando-Chris

Encountered the same problem, @njt1982 don't konw if this is still a problem, but for anyone else in this situation.

From the wiki

RefreshToken This middleware will again try to parse the token from the request, and in turn, will refresh the token (thus invalidating the old one) and return it as part of the next response. This essentially yields a single use token flow, which reduces the window of attack if a token is compromised since it is only valid for the single request.

So it is by design.

From my understanding, you won't be able to combine jwt.auth and jwt.refresh for the same route, that's what I was trying to do at least. Use only jwt.auth and once your frontend detects that the token is invalid, call a route to refresh it, using the refresh middleware or directly the refresh function from the manager.

philperron avatar Apr 14 '17 02:04 philperron

I was having the same issue. Was able to overcome it not by setting

JWT_BLACKLIST_ENABLED=false

Like @njt1982 mentioned as that would open up a vulnerability but I set the blacklist_grace_period to 30 for 30 seconds

JWT_BLACKLIST_GRACE_PERIOD=30

Solved the issue of tokens dying for me and kept the security of having a blacklist

garhbod avatar Sep 05 '17 06:09 garhbod

Okay this only fixed the issue for 30 seconds for me delaying the inevitable 401 - Token has been black listed

I now have two options as I see it.

  1. make a custom refresh middleware or closure where the refresh is called once and simple refreshes the token but doesn't blacklist it straight away
  2. refresh after or before every request

Thoughts?

garhbod avatar Sep 05 '17 23:09 garhbod

Okay the solution for me was the following. I was even able to change the JWT_BLACKLIST_GRACE_PERIOD back to 0.

I changed my refresh api endpoint from

            $api->get('refresh', [ 'middleware' => 'jwt.refresh',
                function() {
                    return response()
                        ->json([ 'message' => 'Token refreshed!' ]);
                }
            ]);

to

            $api->get('refresh', [ 'middleware' => 'jwt.refresh',
                function() {
                    return response()
                        ->json([ 'message' => 'Token refreshed!' ])
                        ->header('Cache-Control', 'no-cache, no-store, must-revalidate');
                }
            ]);

This prevents the endpoint being cached by the browser which means that the new/refreshed token is always received.

garhbod avatar Sep 06 '17 01:09 garhbod

having all of these issues as well, @garhbod's cache method sort of worked but I can still get my token blacklisted sometimes when trying to refresh it.

acidjazz avatar Sep 06 '17 02:09 acidjazz

@acidjazz What frontend are you using? Are multiple requests happening at once?

garhbod avatar Sep 06 '17 04:09 garhbod

@garhbod nuxtjs, I don't think so, I can duplicate the blacklisting event by manually logging in, waiting for it to expire and hitting /refresh either with my browser or postman

acidjazz avatar Sep 06 '17 06:09 acidjazz

please note that you can change config options in your codes in run time. I used this code and this was a solution for me:

        config([
            'jwt.blacklist_enabled' => true
        ]);
        auth()->logout();
        JWTAuth::invalidate(JWTAuth::parseToken());

ivahidmontazer avatar Sep 01 '18 07:09 ivahidmontazer

please note that you can change config options in your codes in run time. I used this code and this was a solution for me:

        config([
            'jwt.blacklist_enabled' => true
        ]);
        auth()->logout();
        JWTAuth::invalidate(JWTAuth::parseToken());

@ivahidmontazer where to change there ?

pwwat avatar Nov 04 '18 14:11 pwwat

please note that you can change config options in your codes in run time. I used this code and this was a solution for me:

        config([
            'jwt.blacklist_enabled' => true
        ]);
        auth()->logout();
        JWTAuth::invalidate(JWTAuth::parseToken());

@ivahidmontazer where to change there ?

You can change this config in your controllers or everywhere else, run time .

ivahidmontazer avatar Dec 14 '18 21:12 ivahidmontazer

I have used this but not work

On Sat, Dec 15, 2018, 02:50 Vahid Montazer <[email protected] wrote:

please note that you can change config options in your codes in run time. I used this code and this was a solution for me:

    config([
        'jwt.blacklist_enabled' => true
    ]);
    auth()->logout();
    JWTAuth::invalidate(JWTAuth::parseToken());

@ivahidmontazer https://github.com/ivahidmontazer where to change there ?

You can change this config in your controllers or everywhere else, run time .

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/tymondesigns/jwt-auth/issues/983#issuecomment-447489465, or mute the thread https://github.com/notifications/unsubscribe-auth/Aj_EwctcWst5U6kAuf5d2cha4Nhxottlks5u5B0ZgaJpZM4LwqTX .

shahid-hussain009 avatar Dec 15 '18 05:12 shahid-hussain009

I have used this but not work On Sat, Dec 15, 2018, 02:50 Vahid Montazer @.*** wrote: please note that you can change config options in your codes in run time. I used this code and this was a solution for me: config([ 'jwt.blacklist_enabled' => true ]); auth()->logout(); JWTAuth::invalidate(JWTAuth::parseToken()); @ivahidmontazer https://github.com/ivahidmontazer where to change there ? You can change this config in your controllers or everywhere else, run time . — You are receiving this because you commented. Reply to this email directly, view it on GitHub <#983 (comment)>, or mute the thread https://github.com/notifications/unsubscribe-auth/Aj_EwctcWst5U6kAuf5d2cha4Nhxottlks5u5B0ZgaJpZM4LwqTX .

I had same problem specially when using multi auth with jwt. You can use a try, catch. when exception happens you can return 'You have logged out successfully'; in catch section. I know this way user will not actually be logged out, but only solution I have found, was this!

ivahidmontazer avatar Dec 16 '18 10:12 ivahidmontazer

nothing work i am using this code in my controller on run time

try 
        {
            config([
            'jwt.blacklist_enabled' => true
            ]);
            \Cookie::forget(JWTAuth::parseToken());
            auth()->logout();
            JWTAuth::invalidate(JWTAuth::parseToken());
            return response()->json(['message' => 'Successfully logged out']);
            
        } 
        catch (Exception $e) 
        {
            return response()->json(['message' => 'There is something wrong try again']);
        }

shahid-hussain009 avatar Dec 17 '18 04:12 shahid-hussain009

nothing work i am using this code in my controller on run time

try 
        {
            config([
            'jwt.blacklist_enabled' => true
            ]);
            \Cookie::forget(JWTAuth::parseToken());
            auth()->logout();
            JWTAuth::invalidate(JWTAuth::parseToken());
            return response()->json(['message' => 'Successfully logged out']);
            
        } 
        catch (Exception $e) 
        {
            return response()->json(['message' => 'There is something wrong try again']);
        }

I set return response()->json(['message' => 'Successfully logged out']); in catch section not in try section. I know this way user will not be logged out, but I didn't fount any other solution.

ivahidmontazer avatar Dec 17 '18 10:12 ivahidmontazer

then what is the good of using

return response()->json(['message' => 'Successfully logged out']);

this code is catch section this way it shows only logout message my aim is not to show only message but logout the user as well what a funny answer it is but thanks for your response it will never work.

shahid-hussain009 avatar Dec 17 '18 11:12 shahid-hussain009

Is this still relevant? If so, what is blocking it? Is there anything you can do to help move it forward?

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs.

stale[bot] avatar Dec 26 '20 02:12 stale[bot]

Is this still relevant? If so, what is blocking it? Is there anything you can do to help move it forward?

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs.

Yes, it's still relevant and not fixed.

Messhias avatar Jun 21 '21 10:06 Messhias

O opened PR #2139 to try to solve this issue and mitigate this exception and make it optional for us.

Messhias avatar Jul 02 '21 18:07 Messhias

@tymondesigns can you review my PR #2139?

Thank you.

Messhias avatar Jul 02 '21 18:07 Messhias