11

I'm using Django REST framework JWT Auth for session creation and permissions, the only problem is: when I log in and after the token expires I can't continue doing the operation I want, unless I log in again. And I didn't fully understand the documentations provided for the additional settings.

So can any one explain a method for dynamically creating (and refreshing) my token (following best practices) so that I can keep doing operations when I'm logged in.

P.S: I'm using angular 2 for my front end, and I'm inserting the token in the Http requests headers. Thanks.

frlan
  • 6,950
  • 3
  • 31
  • 72
Ch_y
  • 356
  • 1
  • 4
  • 16
  • You might want to use [django-rest-framework-refresh-token](https://github.com/lock8/django-rest-framework-jwt-refresh-token), as mentioned in this [answer](https://stackoverflow.com/a/47501051/3015186) to a similar question. – Niko Föhr Nov 26 '17 at 21:21

3 Answers3

26

JWT token refresh is a little confusing, and i hope this explanation helps.

  • tokens have an issued at time (iat in the token)
  • tokens have an expiration date (now() + 1 hour, for example)
  • the token can't be changed. server can only issue a new one
  • iat never changes, but expires does change with each refresh

When you want to extend a token, this is what happens:

  • You send your token to the server endpoint /.../refresh/
  • Server checks its not expired: now() <= token.iat + JWT_REFRESH_EXPIRATION_DELTA
  • If not expired:
    • Issue a NEW token (returned in the json body, same as login)
    • New Token is valid for now() + JWT_EXPIRATION_DELTA
    • The issued at value in the token does not change
    • App now has 2 tokens (technically).
    • App discards the old token and starts sending the new one
  • If expired: return error message and 400 status

Example

You have EXPIRATION=1 hour, and a REFRESH_DELTA=2 days. When you login you get a token that says "created-at: Jun-02-6pm". You can refresh this token (or any created from it by refreshing) for 2 days. This means, for this login, the longest you can use a token without re-logging-in, is 2 days and 1 hour. You could refresh it every 1 second, but after 2 days exactly the server would stop allowing the refresh, leaving you with a final token valid for 1 hour. (head hurts).

Settings

You have to enable this feature in the backend in the JWT_AUTH settings in your django settings file. I believe that it is off by default. Here are the settings I use:

JWT_AUTH = {
    # how long the original token is valid for
    'JWT_EXPIRATION_DELTA': datetime.timedelta(days=2),

    # allow refreshing of tokens
    'JWT_ALLOW_REFRESH': True,

    # this is the maximum time AFTER the token was issued that
    # it can be refreshed.  exprired tokens can't be refreshed.
    'JWT_REFRESH_EXPIRATION_DELTA': datetime.timedelta(days=7),
}

Then you can call the JWT refresh view, passing in your token in the body (as json) and getting back a new token. Details are in the docs at http://getblimp.github.io/django-rest-framework-jwt/#refresh-token

$ http post localhost:8000/auth/jwt/refresh/ --json token=$TOKEN

Which returns:

HTTP 200 
{
    "token": "new jwt token value" 
}
Andrew
  • 8,322
  • 2
  • 47
  • 70
  • Is it really necessary to refresh the token? I mean what if I set the expiration time to 1 day, and every time a user starts the application it asks him to login, thus get a new token (and start from scratch). Does this solution work ? – Ch_y May 05 '17 at 10:12
  • @Ch_y This lets you refresh an existing token that you have in your hands (you could do it every request). If your app wants to, it can store the token in local storage, in a cookie, etc, and re-use it on relaunch. This is common to all tokens, though. If you throw it away then you need to log in again. – Andrew May 05 '17 at 10:18
  • @Ch_y I've tried to explain the JWT extension process better. It confused the hell out of me before. – Andrew May 05 '17 at 10:28
1

I've had same problem in angularjs and I've solved it by writing a custom interceptor service for my authentication headers.

Here's my code:

function($http, $q, store, jwtHelper) {
    let cache = {};
    return {
      getHeader() {
        if (cache.access_token && !jwtHelper.isTokenExpired(cache.access_token)) {

          return $q.when({ 'Authorization': 'Token ' + cache.access_token });

        } else {
          cache.access_token = store.get('token');
          if (cache.access_token && !jwtHelper.isTokenExpired(cache.access_token)) {

            return $q.when({ 'Authorization': 'Token ' + cache.access_token });

          } else {
            return $http.post(localhost + 'api-token-refresh/',{'token': cache.access_token})
            .then(response => {
              store.set('token', response.data.token);
              cache.access_token = response.data.token;
              console.log('access_token', cache.access_token);
              return {'Authorization': 'Token ' + cache.access_token};

            },
            err => {
              console.log('Error Refreshing token ',err);
            }
          );
          }
        }
      }
    };


  }

Here, on every request I've had to send, the function checks whether the token is expired or not. If its expired, then a post request is sent to the "api-token-refresh" in order to retrieve the new refreshed token, prior to the current request. If not, the nothing's changed.

But, you have to explicitly call the function getHeader() prior to the request to avoid circular dependency problem.

This chain of requests can be written into a function like this,

someResource() {
return someService.getHeader().then(authHeader => { 
return $http.get(someUrl, {headers: authHeader}); 

}); }

zaidfazil
  • 9,017
  • 2
  • 24
  • 47
  • In order to get a new token we need to make a POST using the username and the password (of the current user), can you explain what exactly you provided for the new token refresh? or is it another method in Django auth token ? – Ch_y May 04 '17 at 14:36
  • As you can see, the function checks whether there is an existing token on the store(angular-storage, you can also utilise localStorage for this). If there is a token, then it returns a "$q.when" promise. If there isn't any then return a post request to the "api-token-refresh" url, which returns a response with the required new token, which is set to the angular-storage and returned to the caller. – zaidfazil May 04 '17 at 14:58
  • I've just included the function, you need to wrap it in a service and invoke it whenever you are making a request to the django api. – zaidfazil May 04 '17 at 15:05
  • I think this is more important than my answer. Mine just tells you how to turn the feature on (and how it works), but this actually makes use of it. There is also a `verify` endpoint, but its kinda redundant since you can calculate the same thing locally if you know the deltas. – Andrew May 05 '17 at 11:20
  • Appreciate the reply.. feel free to suggest anything comes to your mind.. – zaidfazil May 05 '17 at 13:40
  • Locally, you have to keep that in your mind every time, and its really exhaustive. I just tweaked this only for development purposes. In production, often the token generated has no expiry date unless the user logs out willingly(there are websites which uses the former also). Really appreciate your comment. – zaidfazil May 06 '17 at 09:13
1

just add this line to your JWT_AUTH in settings.py file:

    'JWT_VERIFY_EXPIRATION': False,

it worked for me.