17

To authenticate users in my Angular app i use access tokens with expiry time X seconds and refresh tokens that can be used to prolong the auth for another X seconds.

So the flow is this:

  • A user signs in. Both the access and refresh tokens are stored in local storage
  • A timer is set (5% shorter than X seconds).
  • When the timer is done, a refresh token request is sent to the server and the local storage is updated with the resulting (new) access and refresh tokens.

My problem is this:

  • If I have multiple tabs open, I will inevitably end up in situations where the refresh is triggered from multiple tabs at the same time. The server will accept the first request, but throw a 400 Bad Request - Invalid refresh token for the subsequent requests, since it considers them used.

Does anyone have a good idea how this could be solved? How does one synchronize things across tabs/windows? I have a couple of ideas but they all seem a bit far fetched:

  • If the response is 400 Bad Request, then retry in a little while (or check if there is a valid updated token already).
  • Try to synchronize the server requests across tabs by posting messages between them.
Joel
  • 8,502
  • 11
  • 66
  • 115
  • Probably this could be helpful for you https://stackoverflow.com/questions/20325763/browser-sessionstorage-share-between-tabs – yurzui Jun 12 '17 at 08:48

1 Answers1

3

dont set timer , add interceptor and catch error and if you got 401 error , do your refresh token flow and then repeat failed request with new token

intercept(request: HttpRequest<any>, next: HttpHandler): 
        Observable<HttpEvent<any>> {

return next.handle(request).pipe(
  catchError((error: HttpErrorResponse) => {
    if (error.status == 401) {
      return this.refreshToken(request, next);
     } 
    }
    return throwError(error);
  })
);


      private refreshingInProgress: boolean = false;
  private accessTokenSubject: BehaviorSubject<any> = new BehaviorSubject<any>(null);

  private refreshToken(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    if (!this.refreshingInProgress) {
      this.refreshingInProgress = true;
      this.accessTokenSubject.next(null);
      return this.authenticationService.refreshToken().pipe(
        switchMap((res: any) => {
          this.refreshingInProgress = false;
          this.accessTokenSubject.next(res);
          // repeat failed request with new token
          return next.handle(this.addToken(request, res));
        })
      );
    } else {
      // wait while getting new token
      return this.accessTokenSubject.pipe(
        filter((token) => token !== null),
        take(1),
        switchMap((token) => {
          // repeat failed request with new token
          return next.handle(this.addToken(request, token));
        })
      );
    }
  }
  
  private addToken(request: HttpRequest<any>, token: Credentials) {
    return request.clone({
      setHeaders: {
        Authorization: `Bearer ${token.access_token}`,
      },
    });
  }
farokh veician
  • 224
  • 2
  • 10