The problem is that, when the access token is expired and I do any click, then access token does not refresh. But when I do the second click, it refreshes. For the first click, I get 'GET 401 error'. The actual problem for me is to get access token before any HTTP request in the interceptor.

I tried switchMap for my code to wait for the access token, but it didn't work. Do you have any ideas how could I fix this?

export class DevelopmentInterceptor implements HttpInterceptor {
    constructor(
        private authService: AuthService,
        private router: Router,
        private toastr: ToastrService,
        private translate: TranslateService
    ) { }

intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    const headers = new HttpHeaders({
        'Content-Type': 'application/json; charset=utf-8',
        'Authorization': `Bearer ${this.authService.getAccessToken()}`
    });
    let apiReq = null;
    if (req.url.indexOf('i18n') >= 0) {
        apiReq = req;
    } else if (req.url.indexOf('token') >= 0) {
        apiReq = req.clone({ url: environment.authServerUrl + `${req.url}` });
    } else if (req.url.indexOf('sign-up') >= 0) {
        apiReq = req.clone({ url: environment.signupUrl });
    } else if (req.url.indexOf('api/users') >= 0 || req.url.indexOf('api/roles') >= 0 || req.url.indexOf('api/permissions') >= 0) {
        apiReq = req.clone({ headers: headers, url: environment.authServerUrl + `${req.url}` });
    } else {
        apiReq = req.clone({ headers: headers, url: environment.backenUrl + `${req.url}` });
    }

    if (req.url.endsWith('token')) {
        return next.handle(apiReq).catch((err: any) => { //<--if error use a catch
            if (err instanceof HttpErrorResponse) {
                return this.handleError(err);
            }
        });
    } else {
        return this.authService.checkExpiry().switchMap( (result) => {
            if (result) {
                return next.handle(apiReq)
                    .catch((err: any) => { // <--if error use a catch
                        if (err instanceof HttpErrorResponse) {
                            return this.handleError(err);
                        }
                    });
            }
        } )
    }
}

private handleError(err: Response | any) {
        ...
    }
}

public checkExpiry() : Observable<any> {
    if (!this.cookieService.get('user_id')) {
        this.removeTokens();  // not logged in
        return Observable.of(true);
    } else if (!this.cookieService.check('access_token')) {
        if (this.cookieService.check('refresh_token')) {
            if (this.secondsTillExpiry('refresh_token') > 0) {
                return this.refreshAccessToken().switchMap((data:any) => {
                    if (data) {
                        this.saveTokenInCookies(data);
                        this.updateExpiration(data);
                        return Observable.of(true);
                    }
                })
            } else {
                this.router.navigate(['/login']);
                this.removeTokens();
                return Observable.of(true);
            }
        }
    } else if (this.cookieService.check('access_token') ) {
        return Observable.of(true);
    }
}



public secondsTillExpiry(tokenMode: string): any {
    if (tokenMode == 'access_token') {
        return  ((new Date(1970, 0, 
                 1).setSeconds(jwt_decode(this.getAccessToken()).exp)) - 
                 (Math.round(Date.now()) / 1000));
    } else  if (tokenMode == 'refresh_token') {
        return  ((new Date(1970, 0, 
                 1).setSeconds(jwt_decode(this.getRefreshToken()).exp)) - 
                 (Math.round(Date.now()) / 1000));
      }
    }

private refreshAccessToken(): Observable<Object> {
    const params = 'refresh_token=' + this.getRefreshToken() + 
'&grant_type=refresh_token';
    return this.http.post(this.authUrl, params, this.getOptions());

}

1 Answers

0
Kenana Reda On

you can also check expire token before send any request in CanActivate and if it's valid token so user can is navigated to required route else it will be redirected to login here is an example to handle this

  canActivate(
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
let accesstoken: string = next.queryParams.accesstoken;
if (this.authService.IsAuthenticated) {
  let user = this.authService.GetUser(); 
  let CurrentDate = new Date();
  let date = CurrentDate.getFullYear() + "-" + (CurrentDate.getMonth() + 1) + "-" + CurrentDate.getDate();
  if (user.expire_date) {
    if (Date.parse(date) <= Date.parse(user.expire_date)) {
      if (accesstoken) {
        // if token in url, redirect to url without token :)
        if (user.uuid == accesstoken)
          this.router.navigateByUrl(window.location.pathname);
        // if new token is not the same of current one, Register with new token
        else {
          return this.authService.checkAccess(accesstoken).pipe(
            map(data => {
              if (data === true) {
                if (accesstoken) {
                  this.router.navigateByUrl(window.location.pathname);
                }
                return true;
              } else {
                this.router.navigate(['/login']);
                return false;
              }

            })
          );
        }
      }
      return true;
    }
    else if (Date.parse(date) > Date.parse(user.expire_date)) {
      this.router.navigate(['/login']);
      return false;
    }
  }
}
else {
  this.router.navigate(['/login']);
  return false;
}

}

you should process it according to your code