Jwt-auth: Refresh token

Created on 13 Apr 2015  Â·  53Comments  Â·  Source: tymondesigns/jwt-auth

I use refresh token middleware

Route::group(['prefix' => env('API_VERSION', 'dev'),'middleware' => ['jwt.refresh','jwt.auth','user.lastseen']], function()

I always get token invalid message (token_invalid)?

thanks:D

Most helpful comment

If you can't refresh an expired token, which makes sense, how would the client know when to do it?

No I'm saying you _should_ be able to refresh an expired token. Using jwt.auth on a refresh route interferes with that, because of the timings of befores & afters. Here's a diagram I made previously:

image

Also, couldn't that also cause errors because if it happened to do a refresh at the same time as the user made another request, there could still be an sync issue.

I'm not entirely sure what kind of sync issues you're describing. In normal use, the worst case is that the request is blocked, and the client can just try again (since it should ideally know there was a recent refresh attempt). Even more ideally, the client would know to hold back requests when its performing a refresh or one is imminent, but we can't always get what we want.

Also the grace period helps mitigate this.

Going to check out the development branch. I assume it's stable and safe to use?

Ha. Well soonish the maintainer is going to have an alpha 0.6 release that's stable enough. Develop is pretty turbulent though, because releasing 0.6 has become a moving target with all the important patches that keep coming in.

Any reason why, with a grace period, you couldn't do a refresh on every api call?

Yes this is what I allude to when I specify different kinds of "flows". Plenty of people use a "single-use-token" flow where every call is a refresh. However the existing middleware isn't really appropriate for this use. If you use the refresh middleware alone, you'll end up running the controller method before the check (real bad!), and if you use the two middlewares in tandem, you'll have the situation described above (okayish, but not actually functioning as intended)

Of course plenty of people are doing the latter, and just ignore that refresh_ttl isn't behaving as described and have their tokens only be refreshable while valid...

All 53 comments

looking into this

I am having the same problem since I updeted. The normal auth is working fine. After I authenticate I get the token, save it. When I send it for the firs time I can get trough the middleware. Refresh token sends the header with the new token but that refreshed token gives me back an ivalid token message.

Same for me.

Tried to manually refresh the token with JWTAuth::setToken($token)->refresh(). Can't get it working either. It sends me a new token but it throws a token_invalid exception when attempting to authenticate with this new one.

Same Problem here. Any help on this?

could you try the dev-laravel-5 release to see if that resolves it?

Can not install "tymon/jwt-auth": "dev-master" with laravel v5.0.27, conflict on composer update

dev-master is for laravel 4, you would have to install dev-laravel-5

now i get {"error":"token_expired"}

This is what I experience now:

I use 'middleware' => 'jwt.refresh' on my api routes. For testing I have set ttl' => 1 and 'refresh_ttl' => 5. So I would expect that the token will be refreshed in the 5 minute window. What I experience though is if I not request within the first minute I will get a
"Token has expired" TokenExpiredException in PayloadValidator.php line 74 error.

When I request within 1 minute, the token is refreshed in the Response Header for another minute.

I also would like to customize the response TokenExpiredException in PayloadValidator.php line 74, but don't know how without touching PayloadValidator.php

token_invalid here, on same problem here.
A Tymon\JWTAuth\Exceptions\TokenBlacklistedException instance.

I guess that I found what happens. Note that in the refresh method, old token is added to blacklist cache case enabled:

// Tymon\JWTAuth\JWTManager
    public function refresh(Token $token)
    {
        $payload = $this->decode($token);

        if ($this->blacklistEnabled) {
            // invalidate old token
            $this->blacklist->add($payload);
        }

        // return the new token
        return $this->encode(
            $this->payloadFactory->setRefreshFlow()->make([
                'sub' => $payload['sub'],
                'iat' => $payload['iat']
            ])
        );
    }

And note that in add to blacklist method the key is the jti param from old token payload:

// Tymon\JWTAuth\Blacklist
    public function add(Payload $payload)
    {
        $exp = Utils::timestamp($payload['exp']);

        // there is no need to add the token to the blacklist
        // if the token has already expired
        if ($exp->isPast()) {
            return false;
        }

        // add a minute to abate potential overlap
        $minutes = $exp->diffInMinutes(Utils::now()->subMinute());

        $this->storage->add($payload['jti'], [], $minutes);

        return true;
    }

Thus, when has on blacklist method is called, the old token jti param is the same that the new, so the new token is in blacklist:

// Tymon\JWTAuth\Blacklist
    public function has(Payload $payload)
    {
        return $this->storage->has($payload['jti']);
    }

If you don't need the blacklist functionality just set to false on jwt.php configuration file. But I can't say if it expose to some security vulnerability.

@Maykonn You've hit the nail on the head the head there... not sure how I missed that... maybe I was drunk at the time :beer: :smile:

I will fix this and tag a release as soon as I can

should be fixed as of 0.5.3

Hi, I'm using version 0.5.3 but I still having the refresh token issue, after the first successful request I will get token_invalid error.

I am also using version 0.5.3 and am having the same refresh issue.

When I get this issue, the solution that found to get my project working was to generate a new token with data from older token on each new request. Ex:

Request 1 (GET /login):
Some guest data

Request 2 (POST /login reponse):
User data merged with guest data

This is bad and can generate more issues when you have many async requests and your API(or business core) server is slow.

I will investigate this issue again.

StackOverflow update: http://stackoverflow.com/questions/29703260/laravel-jwt-tokens-are-invalid-after-refresh-them-in-a-authentication-jwt-approa/30110942#30110942

Ok thanks guys, looks like I will have to look at this again

Hi,

I am slightly confused about what is supposed to happen when using ttl, refresh_ttl and the RefreshToken middleware, to be honest.

Here is how I understand this at the moment, theorically:

  • ttl: pretty straightforward. The time for which a token will be valid, i.e. using the token before the end of this time will allow the request to be performed, otherwise a _non authorized_ response will be returned
  • refresh_ttl: if the request is sent after ttl is passed but before the end of refresh_ttl, the actions won't be performed but the token will be refreshed, so the new one can be used to perform the request

That's where I get confused. How is that supposed to happen? Should a _non authorized_ response be made but with a new token sent as a header, to be parsed by the client so it can make the request again?

Also, the RefreshToken middleware sets a new token at every request, but we could as well imagine using a middleware that would do so only when a token is used while in its refresh_ttl period, right? (i.e. not valid but still refreshable)

There is obviously something I quite don't grok here.

Can someone set me straight? :)

Cheers

+1 for comment of @osteel.

Refresh after every request is great, but with (multiple) async requests it goes wrong (token invalid).

@Henri85 regarding my questions above, you'll find some clarification over here

I am trying to get this to work. My native android app does no need to prompt a user to login after the first log in or registration. How can i set the generated token ttl to infinity(similar to a forever cookie unless a user clears his browser cache). If the user looses his phone, uninstall the app or formats his phone, he will only need to log in again and a new token generated and stored in his device, then the old token is expired.
I have read through this thread, and don't really get understand how a refresh token will solve my problem.
My app is intended to work just the way BBM,Whatapp,facebook works.

Any update on this ? jwt.refresh actually doesn't invalidate previous token. But if we have blacklist enabled it just keeps on blacklisting on each request.

Same here

same here, sometime it work perfect , but sometime it become invalid, may i know where is the problem?

@TrustOkoroego Isn't it sufficient to set the ttl to a very high number? For example, 6 million minutes is over 10 years, and it's unlikely anyone would be frustrated with logging in once a decade. You don't have to use refreshing if you don't wish to, but permanent tokens are less secure for users because it means that if a device is lost, the account is completely compromised (_unless_ you keep track of user tokens on the back-end and allow them to be invalidated remotely).

@mrgodhani That sounds like an issue that was already addressed in #218. If so, the fix will be available in the next version.

@youyi1314 Is the token becoming invalid at incorrect times? It is supposed to expire after ttl minutes.

Experiencing this "token_invalid" issue as well.

I'm experiencing this issue too. Looking at the token refresh flow, I found a potential cause:

The jti of the refreshed token is built as follows: md5(sprintf('jti.%s.%s', $sub, $nbf)), with the same sub of the old token (of course!) and a new nbf, but the old one and new one could be produced within the same second. In this case the refreshed token has exactly the same jti of the old one, which has been blacklisted.
If I'm right (I'm not so sure : ), the new token is valid only if it has been produced in a different second than the old one.

Of course in most cases should not be a problem, but using jwt.refresh middleware I think is likely to have more requests in the same second.

Some possible quick and dirty fixes:

Add request time millisecond to the md5 hash
// in Tymon\JWTAuth\PayloadFactory
// function jti()

        $rtf = $_SERVER['REQUEST_TIME_FLOAT'];

        return md5(sprintf('jti.%s.%s.%d', $sub, $nbf, ($rtf - (int) $rtf) * 1000));
Add current timestamp with millisecond resolution to the hash
// in Tymon\JWTAuth\PayloadFactory
// function jti()

        list($micro, $sec) = explode(' ', microtime());
        $millis = (int) (($sec + $micro) * 1000);

        return md5(sprintf('jti.%s.%s.%d', $sub, $nbf, $millis));
Add random salt to the hash
// in Tymon\JWTAuth\PayloadFactory
// function jti()

        return md5(sprintf('jti.%s.%s.%s', $sub, $nbf, bin2hex(openssl_random_pseudo_bytes(8))));

With one of these jwt.refresh seems to work well.

@emilianobovetti I agree that could be problematic. Luckily it has already been addressed on the develop branch:

protected function jti()
{
    return md5(sprintf('%s.%s', $this->claims->toJson(), Str::random()));
}

The new jti function uses the entirety of the claims (so nbf changes will yield a new jti) as well as a random component.

That said, note that the current jwt.refresh is designed to be used _as needed_, at the client's prompting, not on every request, so having this problem with the default middleware would probably indicate issues with your current flow. (See my comments on #186.)

Oops, my bad, I thought jwt.refresh should be used on every request, thanks for the pointer!

Yeah it's a frequent point of confusion, because the usage changed a bit over time. You can still implement a basic 'single-use' flow with your own middleware by running refresh() and setToken($newToken) _before_ $next... but the default flow is to put jwt.refresh on a single endpoint and have the client use it infrequently.

Not sure if this would help or not, but I eventually got it working by doing the following:

class AuthenticateController extends Controller
{
    public function __construct()
    {
        $this->middleware('jwt.auth', ['except' => ['authenticate','refresh']]);
    }

    public function refresh()
    {
        $current_token  = JWTAuth::getToken();
        $token          = JWTAuth::refresh($current_token);

        return response()->json(compact('token'));
    }

I'm writing a single-page-app using EmberJS and am using ember-simple-auth with a JWT addon for authentication. Is there an example somewhere that shows how to setup a token refresh route / routine / best practice? My tokens aren't being refreshed and I'm getting the "token_invalid" error when it tries.

Here's my current routes.php:

Route::group(['prefix'=>'api/v1'], function(){

    Route::post('auth/login', 'Auth\AuthController@authenticate');
    Route::post('auth/refresh-token', ['middleware' => 'jwt.refresh', function(){}]);

    Route::group(['middleware' => ['before'=>'jwt.auth']], function(){
        Route::resource('users', 'UserController');
        Route::resource('organizations', 'OrganizationController');
    });
});

@csprocket777 I got similar problem with you, only difference is i'm using AngularJS. Does anyone know a properly way to solve this problem ?

I used the following when messing around with angularjs.

class AuthenticateController extends Controller
{
    public function __construct()
    {
        $this->middleware('jwt.auth', ['except' => ['authenticate','refresh']]);
    }

    public function refresh()
    {
        $current_token  = JWTAuth::getToken();
        $token          = JWTAuth::refresh($current_token);

        return response()->json(compact('token'));
    }

// routes.php

<?php

Route::get('/', function () {
    return view('welcome');
});

Route::group(['middleware' => 'cors','prefix' => 'api/v1'], function()
{
    Route::resource('authenticate', 'AuthenticateController', ['only' => ['index']]);
    Route::post('authenticate', 'AuthenticateController@authenticate');
    Route::get('authenticate/user', 'AuthenticateController@getAuthenticatedUser');
    Route::get('authenticate/refresh', 'AuthenticateController@refresh');

    Route::resource('jokes', 'JokesController');
});

if ($this->app->environment() == 'local')
{
    Route::get('logs', '\Rap2hpoutre\LaravelLogViewer\LogViewerController@index');
}

@SuperlativeEntity that means you don't use jwt.auth when processing refresh token ? In that way it would prevent token blacklisted, doesn't it ?

Like I said, I have just been messing around with it. I set the refresh ttl to a minute then made multiple calls. If the token expired, I would renew it and continue making the calls. If I made no calls within the minute, the token would expire and be blacklisted.

I'm pretty sure the problem is just related to the refresh middleware blacklisting the old token immediately. If it's possible to add something to the refresh middleware to allow old tokens to be used for 60 seconds, that would most likely eliminate all issues that people are having with their JS apps. It breaks because the new token isn't sent back to the client before a new request comes in, so the new request is still using the old token.

Any idea for a workaround, @tymondesigns?

Edit: Looking through the code, it looks like this is already supposed to be happening. From the function which adds a token to the blacklist:

 $minutes = $exp->diffInMinutes(Utils::now()->subMinute());

Any ideas why is might not be happening like that?

@tucq88 No you don't want to apply jwt.auth on a route that refreshes the token because it will catch expired tokens and stop execution before the refresh can take place. This would totally wreck a "refresh-as-needed" flow, because the client would only try to refresh when it received an expired token and then it _couldn't_ refresh it anymore. It would greatly hinder other flows as well, since a token usually has way more expired-but-refreshable time than valid time.

@mcblum Actually $minutes is just used for the lifetime of the cache entry. Cache entries are only kept around the minimum amount of time necessary (until they would naturally invalidate) to prevent the cache from growing indefinitely. The extra minute is just to prevent any possible edge cases with looking up a blacklisted token in the exact minute it was supposed to expire.

The develop branch _does_ however have grace period functionality. You can set an amount of time for blacklist entries to be ignored after they are created.

Tangent: I am realizing there is a bug in the cache lifetime code that got fixed on develop and not on master. It would cause some blacklisted tokens to become refreshable again (which I know I've seen an issue about around here somewhere...). PR incoming later today.

@tdhsmith How would someone use the refresh thing, then? If you can't refresh an expired token, which makes sense, how would the client know when to do it? Also, couldn't that also cause errors because if it happened to do a refresh at the same time as the user made another request, there could still be an sync issue.

I think grace periods is exactly what I'm looking for. Going to check out the development branch. I assume it's stable and safe to use? Any reason why, with a grace period, you couldn't do a refresh on every api call?

Also, switched to dev-master and the grace period doesn't seem to be working. Do I need to do something else as well?

If you can't refresh an expired token, which makes sense, how would the client know when to do it?

No I'm saying you _should_ be able to refresh an expired token. Using jwt.auth on a refresh route interferes with that, because of the timings of befores & afters. Here's a diagram I made previously:

image

Also, couldn't that also cause errors because if it happened to do a refresh at the same time as the user made another request, there could still be an sync issue.

I'm not entirely sure what kind of sync issues you're describing. In normal use, the worst case is that the request is blocked, and the client can just try again (since it should ideally know there was a recent refresh attempt). Even more ideally, the client would know to hold back requests when its performing a refresh or one is imminent, but we can't always get what we want.

Also the grace period helps mitigate this.

Going to check out the development branch. I assume it's stable and safe to use?

Ha. Well soonish the maintainer is going to have an alpha 0.6 release that's stable enough. Develop is pretty turbulent though, because releasing 0.6 has become a moving target with all the important patches that keep coming in.

Any reason why, with a grace period, you couldn't do a refresh on every api call?

Yes this is what I allude to when I specify different kinds of "flows". Plenty of people use a "single-use-token" flow where every call is a refresh. However the existing middleware isn't really appropriate for this use. If you use the refresh middleware alone, you'll end up running the controller method before the check (real bad!), and if you use the two middlewares in tandem, you'll have the situation described above (okayish, but not actually functioning as intended)

Of course plenty of people are doing the latter, and just ignore that refresh_ttl isn't behaving as described and have their tokens only be refreshable while valid...

@tdhsmith Got it, thank you for such a thorough explanation.

As far as the sync stuff, basically the next request is using what JWT considers to now be a blacklisted token, and fails. When it comes back with 'token_invalid', the client clears the token and forces re-auth.

A Google search didn't turn up any alternatives to the current refresh middleware for a single-use-token flow, which is ideally what we'd want to go to. Really, in this case maybe a custom middleware would be the right choice, I just have no idea how to tell the package to refresh the token while still allowing access to the old token for a minute or so.

Edited to add: there's also the chance I'm doing this completely wrong. The whole point is I just want to have the token expire xxx minutes from when it was last used, with that expiration being updated every time it's used successfully. If it comes back as invalid, expired, or anything else, I want to force the user to re-authenticate.

A Google search didn't turn up any alternatives to the current refresh middleware for a single-use-token flow, which is ideally what we'd want to go to.

I've been holding back on submitting one until I can provide docs on different flows (because this has been a sticking point for people). But maybe I should just submit it anyway.

Really, in this case maybe a custom middleware would be the right choice, I just have no idea how to tell the package to refresh the token while still allowing access to the old token for a minute or so.

If you can't wait until 0.6 stabilizes, you could have a middleware that runs a check of the current token, and if it passes, issues a new token generated immediately using the old sub claim _and_ spawns a delayed queue job to invalidate the old token.

Edited to add: there's also the chance I'm doing this completely wrong. The whole point is I just want to have the token expire xxx minutes from when it was last used, with that expiration being updated every time it's used successfully. If it comes back as invalid, expired, or anything else, I want to force the user to re-authenticate.

I really don't know enough security to state authoritatively whether this is right or wrong. But AFAIK part of the reason for the current design is that being able to chain token refreshes indefinitely seems undesirable. Aside from that I can certainly see the argument that the expired-but-refreshable time period isn't as useful to single-use tokens. IIRC, historically the library was designed for refresh-as-needed so this is probably a byproduct of that direction.

Maybe the long-term option would be to split refresh_ttl into individual_refresh_ttl and descendant_refresh_ttl, where the former governs the time from each token's issue, and the latter governs the time from the original token's issue. Then for your use-case, you could set individual_refresh_ttl = ttl and descendant_refresh_ttl = NULL. Hmmm.

Anyway for now you can exploit the very situation I talked about (mixing jwt.auth with jwt.refresh) to get that effect. Just ensure all of your refreshes are behind something that rejects expired tokens, and turn your refresh_ttl way up, and you'll basically have that system.

I've been holding back on submitting one until I can provide docs on different flows (because this has been a sticking point for people). But maybe I should just submit it anyway.

I think that would be tremendously helpful. I assume I'm not the only person who wants to use a token once but still accept the old one for a minute or so. Also you seem to know exactly what you're doing which is rare.

If you can't wait until 0.6 stabilizes, you could have a middleware that runs a check of the current token, and if it passes, issues a new token generated immediately using the old sub claim and spawns a delayed queue job to invalidate the old token.

I can wait. I'll just set the token expiration for a week or something and remove the refresh middleware from the routes for the time being. I know how much work goes into something like this. No need to be impatient.

...being able to chain token refreshes indefinitely seems undesirable.

I don't know enough about security either, but if I were using my bank site that's how it would work. I can keep using the token as long as I make a request every 5 minutes, but if 5 minutes goes by without a request, the token is then rendered invalid and I'm no longer allowed to continue.

Anyway for now you can exploit the very situation I talked about (mixing jwt.auth with jwt.refresh) to get that effect. Just ensure all of your refreshes are behind something that rejects expired tokens, and turn your refresh_ttl way up, and you'll basically have that system.

I'm going to have to read what you were talking about and try to understand that more. I currently do have auth and refresh on the same routes with ttl turned all the way up but I'm still not seeing how that changes the fact that once that token is blacklisted, it cannot be used anymore for even a second, yet other async requests might come in before the result of the first request (along with the new token) has been returned to the client.

Also, thank you for taking your valuable time on this. I hope I'm not just being annoying but hopefully communicating (or trying to communicate) a valid use case that will be helpful to some other users.

I don't know enough about security either, but if I were using my bank site that's how it would work. I can keep using the token as long as I make a request every 5 minutes, but if 5 minutes goes by without a request, the token is then rendered invalid and I'm no longer allowed to continue.

True, but I suspect banks will also still have a maximum time period you could do this in (i.e. you have to login after a week even if you've been making requests every minute). I'll open up an issue in the next week for discussing the split idea I proposed to support this though.

I'm still not seeing how that changes the fact that once that token is blacklisted, it cannot be used anymore for even a second, yet other async requests might come in before the result of the first request (along with the new token) has been returned to the client.

Sorry, what I was talking about then was disabling the expired-but-refreshable period. To get around the sync issues with multiple requests, you'll either need the grace period feature on develop, or to use the delayed job workaround I suggested above.

Also, thank you for taking your valuable time on this. I hope I'm not just being annoying but hopefully communicating (or trying to communicate) a valid use case that will be helpful to some other users.

No problem! It's very helpful when people have something to contribute more that "it doesn't work". :stuck_out_tongue: And these issues with supporting different security approaches are something the project has been inundated with, so it's great to talk it out.

Why is this closed? This problem is not fixed. It may be with the stuff on dev, but it is not on the stable branch. If it could be reopened, that would be great. @tdhsmith seems to have the correct solution for this. Any timeline on when the grace period will be stable?

@zackify I think the original question was different from where we ended up. Sounds like a fix is coming soon for the issue. Just be patient!

I'll check out develop and confirm that the grace period works, which I'm
sure it will. And hopefully there's a new release soon :)
On Sat, Jan 30, 2016 at 20:54 mcblum [email protected] wrote:

@zackify https://github.com/zackify I think the original question was
different from where we ended up. Sounds like a fix is coming soon for the
issue. Just be patient!

—
Reply to this email directly or view it on GitHub
https://github.com/tymondesigns/jwt-auth/issues/83#issuecomment-177357385
.

I would really love if i could get some information about installing the dev or alpha version so I can use the the grace period for blacklisted tokens. I am having major issues in my angular application with asyn calls when the token is being refreshed. I would just love some docs or a few instruction on how to install the dev/alpha since there are some changes that are not compatible with 5.9

Has this been solved or is there another open issue?

No, this is not solved yet because doesn't possible generate new tokens while the old has expired.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

functionpointdaniel picture functionpointdaniel  Â·  3Comments

marciomansur picture marciomansur  Â·  3Comments

Rasoul-Karimi picture Rasoul-Karimi  Â·  3Comments

aofdev picture aofdev  Â·  3Comments

lloy0076 picture lloy0076  Â·  3Comments