0
votes

I am trying to implement a refresh token feature in my angular 6 application.There are many tutorials on this and I am following http://ericsmasal.com/2018/07/02/angular-6-with-jwt-and-refresh-tokens-and-a-little-rxjs-6/ and https://www.intertech.com/Blog/angular-4-tutorial-handling-refresh-token-with-new-httpinterceptor/ tutorial to achieve. Both basically has the same idea. but i feel the issue I am facing is due to our current structure of calling the refresh token function. Below is the explanation

1) AuthInterceptor

@Injectable()
export class TokenInterceptor implements HttpInterceptor {

 constructor(private authService: AuthenticationService) { }

 isRefreshingToken: boolean = false;
 tokenSubject: BehaviorSubject<string> = new BehaviorSubject<string>(null);

intercept(request: HttpRequest<any>, next: HttpHandler) : Observable<HttpSentEvent | HttpHeaderResponse | HttpProgressEvent | HttpResponse<any> | HttpUserEvent<any> | any> {

return next.handle(this.addTokenToRequest(request, this.authService.getAuthToken()))
  .pipe(
    catchError(err => {
      if (err instanceof HttpErrorResponse) {
        switch ((<HttpErrorResponse>err).status) {
          case 401:
            return this.handle401Error(request, next);
          case 400:
            return <any>this.authService.logout();
        }
      } else {
        return throwError(err);
      }
    }));
}

 private addTokenToRequest(request: HttpRequest<any>, token: string) : HttpRequest<any> {
return request.clone({ setHeaders: { Authorization: `Bearer ${token}`}});
}

private handle401Error(request: HttpRequest<any>, next: HttpHandler) {

if(!this.isRefreshingToken) {
  this.isRefreshingToken = true;

  // Reset here so that the following requests wait until the token
  // comes back from the refreshToken call.
  this.tokenSubject.next(null);

  return this.authService.refreshToken()
    .pipe(
      switchMap((user) => {
        if(user) {
          this.tokenSubject.next(user.accessToken);;
          localStorage.setItem('currentUser', JSON.stringify(user));
          return next.handle(this.addTokenToRequest(request, user.accessToken));
        }

        return <any>this.authService.logout();
      }),
      catchError(err => {
        return <any>this.authService.logout();
      }),
      finalize(() => {
        this.isRefreshingToken = false;
      })
    );
} else {
  this.isRefreshingToken = false;

  return this.tokenSubject
    .pipe(filter(token => token != null),
      take(1),
      switchMap(token => {
      return next.handle(this.addTokenToRequest(request, token));
    }));
  }
 }
}

2) AuthService

Our refresh token service gets called via the following rule

  1. To call a refreshtoken, we send a request with Basic header and a value btoa(someid + someotherid)
  2. In the body of that request we pass the refreshtoken, we received from initial login

      refreshToken() : Observable<any> {
      let currentUser = JSON.parse(localStorage.getItem('currentUser'));
      let token = currentUser.refreshToken;
      let headers = new HttpHeaders();
      headers.append('Authorization', 'Basic '+btoa(someid+someotherid));
      let body = new URLSearchParams();
      body.append('grant_type', 'refresh_token);
      body.append('refresh_token', token); <--As commented in no.2
      return this.http.post("http://localhost:53217/api/Account/Token/Refresh", body.toString())
      .pipe(
            map(user => {
    
      if (user && user.accessToken) {
        localStorage.setItem('currentUser', JSON.stringify(user));
      }
    
      return user;
       }));
      }
    
      getAuthToken() : string {
      let currentUser = JSON.parse(localStorage.getItem('currentUser'));
    
       if(currentUser != null) {
      return currentUser.accessToken;
      }
    
      return '';
      }
    

Observations

  1. It calls refreshtoken method in auth service, but then again from authinterceptor it passes to else block and calls the addTokenToRequest(). So instead of adding the header as Basic it appends Bearer and sends back again 401 error.

The above code uses localstorage, we are using cookies. EG - this.cookieService.get('access_token') , this.cookieService.set('refresh_token', 'ddsdhsdhsdhsdhs')

In brief, my refreshtoken method should first call the API with Basic and then when token received should call the other APIs with the Bearer appended with new token.

Please Help me on this, as i am stuck for couple of days. Thank you

1
You need to not run the request when Basic through the interceptor. A simple solution is to just inspect the route, or possibly use your "isRefreshingToken" variable. So simply put, if (someWayToKnowToAddToken()) // add bearer token; else don't add itJacques ジャック
@Jacquesジャックcan you please elaborate.user6202450
I can try. Essentially, you want to conditionally add the bearer token. So, in your addTokenToRequest method, you can check whatever condition you need to check, and return with the bearer token on one, and without it on the other. (Or with different bearer tokens). Theoretically, you could return before the request if you just don't want it to happen as well.Jacques ジャック

1 Answers

1
votes

Had same issue so I need to check header in the interceptor.

  // exempt some paths from authentication
      if (req.headers.get('authExempt') === 'true') {
        return next.handle(req);
      }

This is all assuming you call service like

 getInfo():Observable<any>{
    return this.http.get<any>(API_URL + 'getDocs', httpOptions);
  }

and your httpOptions looks like

const httpOptions = {
  headers: new HttpHeaders({
   'authExempt': 'true',
 'Content-Type': 'application/json'})
};