tymondesigns / jwt-auth

🔐 JSON Web Token Authentication for Laravel & Lumen
https://jwt-auth.com
MIT License
11.23k stars 1.55k forks source link

Clarification on Refresh Token Behaviour #2241

Open eznix86 opened 4 months ago

eznix86 commented 4 months ago

Clarify JWT Token Behaviour

Explanation

The tymondesigns/jwt-auth access token has an hybrid behaviour. It can be used as a refresh token and as an access token. Once it has passed the expiration time in minutes, It will be invalid as an access token but it will still be valid as a refresh token. When the token is refreshed, the token sent is invalidated (means you cannot use it anymore) and a new token is returned. So once you call ->refresh() it will invalidate the JWT token previously generated.

Exclude refresh in middleware

Endpoint which has a refresh behavoir should not be using the middleware 'auth:api'. Example:

$this->middleware('auth:api')->except(['refresh']);

or do not include it in a group route middleware.

Behavior: Only call refresh() when you need to refresh.

if you want a behaviour where the JSON has a refresh and a access_token, generate a token, then use same for both.

$token = auth()->attempt($credentials); // or ->login($user);
//...
// DO NOT CALL `->refresh()` else it will invalidate the access token.
return [
   "access_token" => $token, 
   "refresh_token" => $token
];

Configuration is for the SAME token

The ttl and refresh_ttl in the jwt.php config is for the same token.

Thanks to: https://github.com/tymondesigns/jwt-auth/pull/2219

Fixes: https://github.com/tymondesigns/jwt-auth/issues/2116 https://github.com/tymondesigns/jwt-auth/issues/2209 https://github.com/tymondesigns/jwt-auth/issues/2205 https://github.com/tymondesigns/jwt-auth/issues/2201 https://github.com/tymondesigns/jwt-auth/issues/2149 https://github.com/tymondesigns/jwt-auth/issues/2136 https://github.com/tymondesigns/jwt-auth/issues/2116 and maybe more ....

@tymondesigns to close all those issues and add this to the docs.

designermonkey commented 3 months ago

I think you're going to need to make a more precise explanation of this as it does not make sense. How are you supposed to utilise the refresh_token you describe above? none of the docs show that token being put into use, and I can't see any actually useful examples on how you would use the refresh_token.

Say we use a jwt ttl of 1 minute, and a refresh ttl of 15 minutes, irrespective of whether we successfully refresh our access, we are unauthenticated after the 15 minute window from the original issued time. Surely, a refresh should change the refresh window to a new issued time plus the 15 minutes? Whatever we do the issued time never changes.

eznix86 commented 3 months ago

I think you're going to need to make a more precise explanation of this as it does not make sense. How are you supposed to utilise the refresh_token you describe above? none of the docs show that token being put into use, and I can't see any actually useful examples on how you would use the refresh_token.

Say we use a jwt ttl of 1 minute, and a refresh ttl of 15 minutes, irrespective of whether we successfully refresh our access, we are unauthenticated after the 15 minute window from the original issued time. Surely, a refresh should change the refresh window to a new issued time plus the 15 minutes? Whatever we do the issued time never changes.

Glad you ask. I am not creating the feature here, I am just describing what happens, as you saw there are a lot of issues opened exactly because of that. Too much even! So it attest that this is an issue rather than the normal behaviour of a refresh token you just described.

TLDR; The issue is here, and my explanation is above is to help people who had the same issue as I had (same for the other long list of issues that has been opened).

PS. Try the package, make a refresh token, and an access token. After trying the package you will understand what is going on!

designermonkey commented 3 months ago

Thanks for your reply, I thought you were describing the solution, not just gathering up the problems. Sorry if I came across rude.

We have the package in place, and issues were raised with us via a PEN test, so we have stumbled upon this not working as expected.

My expected behaviour is that there shouldn't be any kind of physical refresh token at all, that is an OAuth2 thing, not a JWT thing. I would expect though that the refresh TTL exists and is used properly.

The problem I see is that the iat never changes in the token, so it will always expire at the refresh_ttl time irrespective. That makes this quite a simple bug to fix IMO.

designermonkey commented 3 months ago

https://github.com/tymondesigns/jwt-auth/blob/51620ebd5b68bb3ce9e66ba86bda303ae5f10f7f/src/Manager.php#L177

This line is incorrect, as far as I can see. A refreshed token should always receive a new iat.

eznix86 commented 3 months ago

Unfortunately the maintainer is not here for unknown reasons.

I would recommend to fork the repo and fix the issues.

That's why also I open the issue because the maintainer is not anymore present on this package. No one can merge fixes to existing issues 😕.

I even email the maintainer. No reply.

amos-yau commented 1 month ago

@eznix86 did you fork it and fixed? I encountered the same issue, this is really a bug that the 'iat' is not refreshing, so the new token will be expired and unable to be refreshed if it's 'exp' passed/hit the origin 'refresh_ttl' (hope my understanding to the TTL and REFERSH_TTL is correct)

The bug with a fixed 'iat':

token 1          :      |iat|       |exp|                           |refresh_ttl|
token 1-refreshed:      |iat|              |exp|                    |refresh_ttl|
token 1-refreshed:      |iat|                     |exp|             |refresh_ttl|
...
token 1-refreshed:      |iat|                                  |exp||refresh_ttl| <<< user must be kicked out

My understanding, so user won't be kicked out if he keep refreshing the token:

token 1          :      |iat|       |exp|                    |refresh_ttl|
token 1-refreshed:             |iat|       |exp|                    |refresh_ttl|
token 1-refreshed:                    |iat|       |exp|                    |refresh_ttl|

@designermonkey is this aligned to your view too?

eznix86 commented 1 month ago

@eznix86 did you fork it and fixed? I encountered the same issue, this is really a bug that the 'iat' is not refreshing, so the new token will be expired and unable to be refreshed if it's 'exp' passed/hit the origin 'refresh_ttl' (hope my understanding to the TTL and REFERSH_TTL is correct)

The bug with a fixed 'iat':

token 1          :      |iat|       |exp|                           |refresh_ttl|
token 1-refreshed:      |iat|              |exp|                    |refresh_ttl|
token 1-refreshed:      |iat|                     |exp|             |refresh_ttl|
...
token 1-refreshed:      |iat|                                  |exp||refresh_ttl| <<< user must be kicked out

My understanding, so user won't be kicked out if he keep refreshing the token:

token 1          :      |iat|       |exp|                    |refresh_ttl|
token 1-refreshed:             |iat|       |exp|                    |refresh_ttl|
token 1-refreshed:                    |iat|       |exp|                    |refresh_ttl|

@designermonkey is this aligned to your view too?

Lol, no, the maintainer does not respond anymore. Basically just follow the thing I explained earlier you should be good, either you abide by it or find another package.

eznix86 commented 2 weeks ago

I believe due to the absence of the maintainer. Use Laravel Sanctum instead.

Documentation of Laravel Sanctum: https://laravel.com/docs/11.x/sanctum#issuing-api-tokens

Note: Sanctum creates only a token which can be reused.

To perform access and refresh token behaviour I would recommend to look at: https://medium.com/@marcboko.uriel/manage-refresh-token-and-acces-token-with-laravel-sanctum-85defbce46ed https://muhamadhhassan.me/creating-refresh-tokens-with-laravel-sanctum

Note: I am not the author of those posts.