How do you handle token refreshing?


#1

I’m using JWTs for authentication. The token is valid for 1 hour. The app will be a browser based app & ios app. I have around 4 pages (Login, Category search, Product search, confirmation/add to order). Every single one of these pages makes HTTP requests to my back end so I have to have a valid token for each one.

I can think of two solutions

  1. On login, schedule a refresh every 59 minutes. But will this work well for a browser app? Say for example a user leaves their browser open and comes back after 2 hours and starts searching for a product but the token is now expired so the backend is returning an error

  2. Make a refreshToken() function that is called before every HTTP request like

    search(keyword): Observable
    {
    //refresh the token now
    refreshToken();
    return this.http.get(’’)
    .map(
    (response: Response) => {
    return response.json();
    },
    (error: Error) => {
    console.log(error);
    }
    );
    }

But what if the token expires in the middle of the user searching?

Thoughts? How do you handle token refreshing in your apps?


#2

You can improve method 2 by isolating it into a token provider. When the token provider is asked for a token, it can look at the exp claim of the one it has, and retrieve a new one only if needed.


#3

Yeah thats a good idea. What im not sure about is if it will break my app if the token expires in the middle of the searches (example: user begins searching and 10 secs later the token expires while the user is still typing).


#4

Should not be a problem. The token provider service is responsible for ensuring that you always have a valid token, and the authenticated http service will only make requests with valid tokens.


#5

So in the token provider I can just use something like angular2-jwt’s tokenNotExpired() function that returns true or false depending on if the token is in storage and hasnt expired yet… Will it really be this simple?

 get token
 check if its expired
    return true or false

#6

Not quite, because you’ll have to return some future (Promise or Observable) in order to handle the situation where the token was expired and needs to be refreshed.


#7

Oh right of course. This is what I came up with real quickly, would it work? Or is there a better way to do it?

 get token
 if token expired
    refreshToken();

refreshToken() {
    return this.http.get("")
    .map((response: Response) => {
        var  token: any = response.json();
          storage.set('token', token);
     }

#8

More or less, but map should not modify state, so I would write it more like this:

getToken(): Observable<string> {
  let rv: Observable<string>;
  if (!this.isTokenValid()) {
    rv = this.http.get(url).map(rsp => rsp.json());
    rv.subscribe(token => {
      this.token = token;
      this.storage.set('token', token)
    });
  } else {
    rv = Observable.of(this.token);
  }
  return rv;
}

#9

Thanks I’m going to give this a try right now. What does this line do? rv = Observable.of(this.token);

The documentation says that of Converts arguments to an observable sequence. So you can subscribe to it and stuff? But would there be any point to that?


#10

It makes the return value consistently an Observable, so that the outside world (callers of this function) need not know or care whether the token was cached or needed to be fetched anew.


#11

Thanks for your help @rapropos here is what sort of works for my app in case anybody else needs help in the future.

 get(): Promise<any> {
    return this.storage.get('token_id').then(token_id => {
      this.token_id = token_id;
    });
  }

  set(token_id) {
    this.storage.set('token_id', token_id);
  }

  refresh(): Observable <any> {

    let obs = Observable.fromPromise(this.gettoken_id())
      .filter(() => this.jwtHelper.istoken_idExpired(this.token_id))
      .flatMap(() =>  this.authHttp.get(''));
    obs.subscribe((response: Response) => {
      this.token_id = response.json().token_id;
      this.settoken_id(this.token_id);
    }, (error: Response) => {
      console.log(error);
    });
    return obs;
  }

I just call refresh() before my http calls. However there is one which I am still trying to figure out. Say a user is typing “Hel” in the search bar and the token expires but then they type “l” a call is sent to my API without a token but if they type “o” it sends a call to my API with the token so the token is not getting stored before the call is made. I will post here if I find a solution or why its happening


#12

Please use string instead of any.

There is one very subtle bug in get(). If you are intending for somebody to be able to do:

this.tokenService.get().then((jwt) => {
  // woot I got a JWT
});

…it won’t work. The then clause in get() swallows the token. You either need to explicitly do this:

get(): Promise<string> {
  return this.storage.get('token_id').then((token) => {
    this.token_id = token;
    return token;
  });
}

or you need to write it like this:

get(): Promise<string> {
  return this.storage.get('token_id').then(token => this.token_id = token);
}

This special form (with no {} around the code block) implicitly returns the expression on the right hand of the =>.

All of this is mooted by the fact that get() shouldn’t hit storage every time it is called. A better strategy is to do it once in the constructor:

token: string;
constructor(private _storage: Storage) {
  _storage.ready().then(() => _storage.get('token_id'))
    .then(token => this.token = token);
}

get(): Observable<string> {
  if (!this.jwtHelper.isValid(this.token)) {
    return this.authHttp.get('').map((rsp) => {
      this.token = rsp.json().token_id;
      this._storage.ready().then(() => this._storage.set('token_id', this.token));
      return this.token;
    });
  } else {
    return Observable.of(this.token);
  }
}

I don’t see a need for set(), because presumably the only source of valid tokens comes from calls to get() when there wasn’t one at the time. jwtHelper.isTokenValid() needs to be able to handle the situation where it is passed something null or undefined, and consider that invalid. That solves your initialization case, at the relatively small cost that you will hit the backend (perhaps needlessly) if get() is called before the initial storage read resolves. If that is a concern to you, you could guard against it by waiting on the initial read in get().


#14

I finally got a chance to try and implement this and I’m still having the same problem. I tried all the ways you listed! Also the last get() method doesn’t work for me it never even calls the API I have to do it this way

refresh(): Observable <any> {

    let obs = Observable.fromPromise(this.getToken())
      .filter(() => this.jwtHelper.istokenexpired(this.token_id))
      .flatMap(() =>  this.authHttp.get(''));
    obs.subscribe((response: Response) => {
      this.token_id = response.json().token_id;
      this.settoken(this.token_id);
    }, (error: Response) => {
      console.log(error);
    });
    return obs;
  }

If I do it the way you suggest I get a “token not provided” error


#15

I can’t see how that could be.

There are three possible states for this.token at the time get() is called:

  • falsy: null or undefined
  • truthy yet invalid
  • truthy and valid

In either of the first two cases, jwtHelper should tell us the token is invalid, and we will follow the if path which calls authHttp.get() (presumably what you mean by “calls the API”). In the third case, we follow the else path with no need to hit the API because our token is valid.


#16

o I logged my token on login and also in the get(): method as the first line and they are different. I also logged the token in the constructor after storage.get() and it says undefined


#17

Can you post the jwtHelper class? I want to make certain that it handles falsy values correctly.

If by this, you mean something like this:

constructor(storage: Storage) {
  storage.ready().then(() => storage.get('token_id'))
    .then(token => this.token = token);
  console.log(this.token);
}

That’s the nature of the beast. The only place you can put the log and have it have any meaning is inside the then clause right after you have assigned to this.token. Not outside.


#18

I see. Thats how I was logging it so I guess thats why.

public isTokenExpired(token: string, offsetSeconds?: number): boolean {
    let date = this.getTokenExpirationDate(token);
    offsetSeconds = offsetSeconds || 0;

    if (date == null) {
      return false;
    }

    // Token expired?
    return !(date.valueOf() > (new Date().valueOf() + (offsetSeconds * 1000)));
  }
  }

Here is the full Angular-JWT package source code


#19

I don’t know how more clearly I could have stated this:

It doesn’t, which is causing your problem. You need to do something like this:

isTokenValid(): boolean {
  return this.token && !this.jwtHelper.isTokenExpired(this.token);
}

…or any other solution you find more readable, but it has to consider falsy tokens invalid.


#20

its still not working because this.token is storing the token from the last session even when I reload. I guess I have to clear the storage but still if a new token is set on login shouldnt it clear the old one and the constructor in my token provider get the new one from storage?


#21

I really wish you would bother to read the things I write and explain where my assumptions aren’t valid. Again,

Now you are telling me that “a new token is set on login”, so the tokens aren’t just coming from the authHttp.get() call inside get(). If new tokens are coming in from somewhere else, you need to update both storage and the stored token property of the TokenProvider.