tymondesigns / jwt-auth

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

Access tokens vs refresh tokens? #1105

Open tremby opened 7 years ago

tremby commented 7 years ago

Is there any concept of access tokens vs refresh tokens in the context of jwt-auth?

In resources I am reading about JWT, they appear to be separate things, but as far as I can tell there's only one kind of token being handed out in jwt-auth. There's probably something here I'm not understanding.

pvanhemmen commented 7 years ago

Yes that's the thing that confuses me the most, too. While jwt-auth has the possibility to refresh the token if it's expired, it's not doing this by using a seperate refresh token, right? So all I can do is check the token itself for validity and issue another one with the response if it's expired. In the Application I then have to set the token to whatever comes with the response. That's how I understand it right now.

yilliot commented 7 years ago

im reading this https://auth0.com/blog/refresh-tokens-what-are-they-and-when-to-use-them/

tremby commented 7 years ago

I'm not sure how that helps. I already understand what the difference is, or I think I do. What I'm pointing out, and asking about, is that this package jwt-auth appears to only have refresh tokens and no regular tokens, unless I'm misunderstanding something. There aren't two different types of tokens it can generate as far as I can tell.

mesqueeb commented 7 years ago

This is what I learned from playing around with it:

  1. JWT auth has only 1 kind of tokens. Access tokens.
  2. Set the ttl short to eg. 1 day.
  3. you can refresh your token even after it's expired with something like:
    public function refreshToken(Request $request)
    {
        return JWTAuth::refresh($request->only('token')['token']);
    }
  4. Save the token request date: new Date() in your SPA together with the token.
  5. In this case, if an api call is made and the token is expired (more than 24 hours has passed) the api calls won't work. Therefore: on every API call just do a quick check, if the difference between new Date() and the token request date is bigger than 24 hours or not.
  6. In case more than 24 hours have passed, intercept the api call, first make a "refreshToken api call" → see 3) and then AFTERWARDS make the api call with the refreshed token. In my case, every API call does this:

    let now = new Date();
    let diff = getDateDiff(now, state.tokenTimeStamp, 'minutes');
    let tokenLifeSpan = 1439; // 1 day - 1 min
    if (diff > tokenLifeSpan)
    {
    if (window.refreshingToken)
    { 
        // this is to prevent an endless loop
        console.log('refreshingToken...');
        return;
    }
    window.refreshingToken = true;
    
    console.log(`old Token: ${state.token}`);
    console.log(`old Token Date: ${state.tokenTimeStamp}`);
    // here I start refreshing the token:
    axios.post(apiBaseURL+'refreshToken', {token:state.token})
    .then(({data}) => {
        window.axios.defaults.headers.common = {
            'X-Requested-With': 'XMLHttpRequest',
            'Authorization': "Bearer " + data ,
        };
        state.token = data;
        state.tokenTimeStamp = new Date();
    
        console.log(`new Token: ${state.token}`);
        console.log(`new Token Date: ${state.tokenTimeStamp}`);
        window.refreshingToken = false;
        // then I do the API call once more, after having made sure the new token is set properly:
        dispatch('patch', {id, field, value});
    });
    console.log('stop here');
    return;
    }
tremby commented 7 years ago

I'm not sure why you think you need to pass a date with the token. Tokens already have their expiry date stored within them.

One way to get it is with the jwt-decode package:

import jwtDecode from 'jwt-decode';

const decoded = jwtDecode(accessToken);
const expiresAt = new Date(decoded.exp * 1e3);

And then just compare the current date with expiresAt.

But yes, I'm already using similar logic to transparently refresh a token if expired before making the API call.

tremby commented 7 years ago

I looked over the JWT specification and you're right that there's no such thing as a special refresh token.

So I think this ticket is actually asking exactly what I asked in #1146: how can I make a token which cannot be refreshed?

I guess it'd have to be a change to the logic in either the refresh functionality in this package, or a higher-level change to the logic in the refresh middleware, which would only allow a token to be refreshed if it has a refreshable claim.

DoDSoftware commented 7 years ago

@mesqueeb That seems correct. I really wish that were explained in the wiki. The tiny section on refreshing tokens doesn't really tell us much. Especially since this single token approach seems to unlike most other JWT services.

jampack commented 6 years ago

@tremby he is referring the new Date/Time on the client side as client can not decode the token and can only know how long the token will live which also has to be disclosed by the server

tremby commented 6 years ago

@akkhan20: I think I must be misunderstanding you. I don't know why you're saying the client cannot decode the token. The client can absolutely decode the token. I pointed out how a couple of messages back.

jampack commented 6 years ago

@tremby jwtDecode(accessToken) will never give you a decoded token with its parameters though u can give it a try and please report if it do coz that will be a massive security issue.

tremby commented 6 years ago

What are you talking about? JWT payloads are just base64url-encoded, not encrypted. https://jwt.io/introduction/#payload

jampack commented 6 years ago

@tremby the access token generated by this package is encrypted with a secret key on the server that client would never know about so it cant be decrypted on the client and what's the point of an access token that a client can read, isn't it that anyone can generate one alike if they know what information it consists of? image

The site you referred to also consists of a debugger and you can generate a token through this package and try decrypting it with that without the jwt_secret on the server.

tremby commented 6 years ago

Go and read the specifications, both of JWT and of HMAC. The only thing happening cryptographically is signing the token. The payload is not encrypted. There's nothing wrong with the client reading their own access token; they already know who they are. No, a user cannot modify or create a new valid access token, because they don't have the secret key and so can't sign the access token. This is a waste of my time, and off topic, and I won't respond to you any more.

tymondesigns commented 6 years ago

@tremby so as it stands at the moment, a refresh token IS an access token, so there are no tangible "refresh tokens".. just that you can refresh a token using an existing token (given it's within the required refresh_ttl)

Having said that, I have been thinking a while that this would be better to have a distinctly separate "refresh_token" that is merely used to retrieve an access token. And I'll be looking at this for the (1.1 / 2.0) release

Does that make any sense?

@akkhan20 as @tremby has said, I think you need to do a little reading :) https://scotch.io/tutorials/the-anatomy-of-a-json-web-token

tremby commented 6 years ago

Sounds good. I suppose it might be possible to hack it in right now, by adding a custom "refreshable" claim to a token, and refusing to refresh it if that claim is not present. But yeah, it'd be great if this were built in.

jampack commented 6 years ago

@tymondesigns thanks for the reference i get it now and @tremby thanks for pointing out the incorrect concept

boukeversteegh commented 6 years ago

Man this is so confusing. The JWT specification doesn't even include the concept of a refresh token, yet many resources talk about it as if its part of JWT. Apparently refreshing tokens is a custom job, and can be done in many ways.

I came to this package assuming there exist "refresh tokens", but couldn't find it after digging and digging through the source code, and documentation (although small).

My suggestion is to add a "refresh tokens" page to the wiki explaining that there are no refresh tokens, and that the refreshing is done using the original access token.

DoDSoftware commented 6 years ago

@boukeversteegh agreed. This odd concept was by far the largest hurdle to using this package. There should be solid examples and explanations in the docs.

buglinjo commented 5 years ago

As I know access tokens and refresh tokens should be stored differently too on the client side. Access tokens can be "not as securely" stored as refresh tokens. What happens if let's say hacker gets access token from you? They can refresh and refresh it forever am I right? Can anyone explain it to me why we only have access tokens here and not using refresh tokens? I think it's very insecure...

henzeb commented 3 years ago

For anyone landing on this page being confused as "the original specs did not specify a separate refresh token" . Apparently this was considered a major security issue. An access token is then stored in either cookie or localStorage, both are prone to CSRF attacks. anyone can use this token to refresh when stolen and use it "forever". @buglinjo is right: a refresh token is stored in a httponly cookie. More on this topic: https://hasura.io/blog/best-practices-of-using-jwt-with-graphql/#jwt_persist & https://owasp.org/www-community/HttpOnly

ElForastero commented 3 years ago

It's unsecure to store access_token persistent in the browser.

It should be stored in memory and requested on every app startup. The refresh_token is different and should be stored in the only one secure place in the browser - in the httpOnly cookie (better with path like /api/auth/refresh).