Token being refreshed but not being sent with new request. Why?


#1

Here is my problem. A user types “AB”, the token is expired, the user types “C” and a call is made to refreshToken() but I get a token_not_provided error, user types “D” and it works fine because the token has been refreshed and stored in time.

Does anybody have any solutions for this? Can I delay the call with a timer if the token is expired? Do I need to fork the calls so they are made in parallel?

refreshToken() {

  if (this.jwtHelper.isTokenExpired(this.token)) {
    this.authHttp.get('http://test.app/api/refresh?token=' + this.token)
      .map(res => res.json())
      .subscribe(data => {
        this.setToken(data.token);
        // this.storage.ready().then(() => this.storage.set('token', data.token));
        console.log("in refresh");
      },
      err => console.log("refreshToken()", err) )
  }
}

Getting the token

jwtHelper: JwtHelper = new JwtHelper();
token;

constructor(private authHttp: AuthHttp, private storage: Storage, private alertCtrl: AlertController, private http: Http) {
  storage.ready().then(() => storage.get('token'))
    .then(token =>
    {

      this.token = token;
      this.checkToken(token);
    });
  }

searchService.ts


search(keyword): Observable<any>
  {
    this.auth.refreshToken();
    return this.authHttp.get('http://test.app/api/search/' + keyword)
      .map(
        (response: Response) => {
          return response.json().jobSites;
        },
        (error: Response) => {
          console.log(error);
        }
      );
}

in component

onSearch(event)
  {
    let keyword = event.target.value;

    this.searchService.search(keyword)
      .subscribe(
        (loc: Home[]) => this.loc = loc,
        (error: Response) => {
          console.log(error);
          //alert("Session Expired. You must login again.");
        }
      )
}

app.module.ts

export function getAuthHttp(http, storage) {
    return new AuthHttp(new AuthConfig({
      headerPrefix: '',
      noJwtError: true,
      globalHeaders: [{'Accept': 'application/json', 'Content-Type': 'application/json', 'X-Requested-With': 'XMLHttpRequest'}],
      tokenGetter: (() =>   storage.ready().then(() => storage.get('token')))
    }), http);
}

#2

I could swear that I gave you a design that was both more efficient and immune to this problem, but I guess you really didn’t like it.

If you insist on doing things this way, you must return some sort of future out of refreshToken() and wait on it before calling anything dependent on the token.

I also think you’re asking for trouble relying on the token being stored in two places at once, always synchronized. I would only read the token from storage once, at application startup. The way I read your code, you’re hitting storage on every single http request. That is both inefficient and error-prone.


#3

If you’re talking about this way it doesn’t work for me. It doesnt even call my refresh endpoint and I get a “token not provided error” which is weird because that means somehow the token is being deleted. I have no idea why its doing that

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

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

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

I am going to change my code so it only checks for the token once. Thanks


#4

Presumably you made isTokenValid an object method, so you would have to invoke it like so:

if (!this.isTokenValid()) {

Please also note that I am assuming that this.authHttp.get('') returns something containing a token property that is useful. If that’s not the case, you have to replace that part with whatever gets a valid token from the backend.

I have tested the following in a scratch project and it works as intended:

@Injectable()
export class TokenService {
  token: string;
  expired: boolean;

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

  scrambleToken(): void {
    this.token = Math.random().toString(36).slice(2);
  }

  get(): Observable<string> {
    if (!this.isTokenValid()) {
      return Observable.of({token: Math.random().toString(36).slice(2)})
        .delay(3000)
        .map((rsp) => {
        this.token = rsp.token;
        this.expired = false;
        return this.token;
      });
    } else {
      return Observable.of(this.token);
    }
  }
}

export class HomePage {
  token: string;

  constructor(private _tokens: TokenService) {
  }

  getToken(): void {
    this._tokens.get().subscribe(token => this.token = token);
  }

  expireToken(): void {
    this._tokens.expired = true;
  }

  scrambleToken(): void {
    this._tokens.scrambleToken();
  }
}

<div>{{token}}</div>
<button ion-button (click)="getToken()">get token</button>
<button ion-button (click)="expireToken()">expire token</button>
<button ion-button (click)="scrambleToken()">scramble token</button>

If I press the “scramble” button, and then the “get” button, I see a random string immediately. If I click the “expire” button and then the “get” button, I see a random string after a 3 second delay, indicating that the “backend” was hit. I still think the fundamental skeleton of that code is solid.


#5

Yes thats how I was invoking it I just copied it from the other thread and pasted it here. A token is being returned from the back end in other calls. Super weird it doesnt work.

I’ll give it another try tomorrow but if it doesn’t work how would I “return some type of future” I dont get what you mean by that. How would I modify my current code to be able to do that?


#6

I mean that refreshToken() must return either a Promise or an Observable, and people calling it must only do things that require the token inside then or subscribe off refreshToken()'s return value.

As for how to modify your current code, any suggestion I make is just going to look like what I’ve already suggested.

What you have now is not going to be easy to convert, because you have written refreshToken() so that it’s essentially meaningless. If you call it and the token was valid, you are safe to proceed, but in that case there wasn’t any need for you to have called refreshToken(). If you call it and the token wasn’t valid, then the token will become valid at some point in the future, but you don’t know when, so you have no idea when it is safe to try to fire http requests.


#8

Finally got it working. Thanks @rapropos

search(): Observable<any> {
  return this.auth.get()
   .flatMap(()=>this.authHttp.get(''))
      .map(
        (response: Response) => {
          return response.json().info;
        },
        (error: Response) => {
            console.log(error);
        }
      );
}

search(): Observable<any> {
  return this.auth.get('')
   .flatMap(()))
      .map(
        (response: Response) => {
          return response.json().info;
        },
        (error: Response) => {
            console.log(error);
        }
      );
}