Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Access tokens vs refresh tokens? #1105

Open
tremby opened this issue Apr 12, 2017 · 21 comments
Open

Access tokens vs refresh tokens? #1105

tremby opened this issue Apr 12, 2017 · 21 comments

Comments

@tremby
Copy link

tremby commented Apr 12, 2017

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
Copy link

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
Copy link

yilliot commented Jun 19, 2017

@tremby
Copy link
Author

tremby commented Jun 19, 2017

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
Copy link

mesqueeb commented Aug 23, 2017

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']);
    }
  1. Save the token request date: new Date() in your SPA together with the token.
  2. 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.
  3. 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
Copy link
Author

tremby commented Aug 24, 2017

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
Copy link
Author

tremby commented Aug 24, 2017

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
Copy link

@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
Copy link

jampack commented Dec 27, 2017

@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
Copy link
Author

tremby commented Dec 28, 2017

@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
Copy link

jampack commented Jan 16, 2018

@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
Copy link
Author

tremby commented Jan 16, 2018

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

@jampack
Copy link

jampack commented Jan 16, 2018

@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
Copy link
Author

tremby commented Jan 16, 2018

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
Copy link
Owner

tymondesigns commented Jan 16, 2018

@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
Copy link
Author

tremby commented Jan 16, 2018

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
Copy link

jampack commented Feb 3, 2018

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

@boukeversteegh
Copy link

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
Copy link

@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
Copy link

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
Copy link

henzeb commented Dec 3, 2020

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
Copy link

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests