1

I am trying to implement an access_token/refresh_token into my Http Interceptor in Angular5. The problem I have is I cannot do the call again after I get the refresh token. So, when I get a 401 error (access_token expired), I get a new access_token using the refresh_token and everything works fine until here. Next, I should do the initial call again using the new access_token, so I am trying to do this with the second

return next.handle(req);

but this doesn't work. So when I click on the first time and the token is expired, the application successfully gets a new token and writes it into localStorage, but the initial call is not being made again. If I click again, the initial call is being made successfully (with the new access token which was stored properly on the previous click). It's like I cannot get out of the catch. Maybe I am doing a stupid mistake.

Bellow is my code.Please advise. Thanks!

intercept(req: HttpRequest<any>, next: HttpHandler): Observable<any> {
    let access_token, refresh_token;
    if (localStorage.currentUser) {
        access_token = JSON.parse(localStorage.currentUser).access_token;
        refresh_token = JSON.parse(localStorage.currentUser).refresh_token;

        if(access_token){
            req = req.clone({ headers: req.headers.set('Authorization', 'Bearer ' + access_token) });
        }
    }
    return next.handle(req)
        .catch((error, caught) => {
            if (this.isAuthError(error)) {
                return this._auth.getRefreshToken().map((resp) => {
                    let user = resp.json();
                    localStorage.setItem('currentUser', JSON.stringify(user));
                    access_token = JSON.parse(localStorage.currentUser).access_token;
                    req = req.clone({ headers: req.headers.set('Authorization', 'Bearer ' + access_token) });
                    return next.handle(req);
                }); 
            } else {
                return Observable.throw(error);
            }
        })
}

3 Answers 3

2

You need to use flatMap() instead of map() as shown below:

        ....
        if (this.isAuthError(error)) {
            return this._auth.getRefreshToken().flatMap((resp) => {
        ....                                    ^^^^^^^
                                                 here

Otherwise your solution looks good to me.

Sign up to request clarification or add additional context in comments.

2 Comments

Thanks @Alexander! It seems flatMap did the trick. I also had to add import 'rxjs/add/operator/mergeMap'; in order to use flatMap, but I believe this depends on the Angular version. Many thanks for your help!
@decebal, please accept the answer if it worked. I'm glad I was able to help anyway. :)
0

In your intercept you need to set the headers.

if(access_token){
            req = req.clone({setHeaders: 
            {Authorization: `Bearer ${access_token}`}
        });
}

You'll need to do the same for the other token.

1 Comment

I already did that. Anyway, that works fine. The problem I have is with the code from the catch.
0

I did something like:

if ( this._auth.isValidToken() )
  token = this._auth.getValidToken();
  req = this.addToken(req, this._auth.getToken());
  return next.handle(req);
}

// The getNewAccessToken is an observable
return this._auth.getNewAccessToken().pipe(switchMap(() => {
    const clone: HttpRequest<any> = this.addToken(req, this._auth.getToken());
    return next.handle(clone);
})).catch(error => {
  if ( error.error === 'invalid_token' ) { // if it fails to refresh the token
    // Redirect to Login, but you could also cache the url
    this.router.navigate(['/login']);
    return Observable.throw('Login expired');

  }
  console.log(error);
  return Observable.throw(error);
});

The getNewAccessToken() function looks like is:

  public getNewAccessToken(): Observable<string>  {
    const headers = new HttpHeaders()
    .set('Content-Type', 'application/x-www-form-urlencoded') // FIXME: Replace Encoded Base64 for a custom version
    .set('Authorization', 'Basic WQQPWEOIRUPOIUQ=');

    const params = new HttpParams().set('grant_type', 'refresh_token');
    const httpOptions = { headers: headers, params: params, withCredentials: true };

    return this.httpClient.post( this.oauthTokenUrl,  new FormData(), httpOptions )
      .map(success => {
        this.storeToken(success['access_token']);
      })
      .catch(this.handleError);
  }

The storeToken function:

  public storeToken(token: string) {
    this.jwtPayload = this.jwtHelper.decodeToken(token);
    localStorage.setItem('token', token);
    this.subjectUser.next( { name: this.jwtPayload.name, email: this.jwtPayload.user_name } );
  }

4 Comments

So in getNewAccessToken() you retrieve the new access_token, but where do you use it to get the data you need from the API? I don't have problem getting a new token, my problem is I cannot make the initial HTTP request from inside the 'catch' after I get the new access_token. Also, on your code, you don't call getNewAccessToken() anywhere.
I fixed the getNewAccessToken() call. The sequence is:
I fixed the getNewAccessToken() call at the example. I had problems using that too, because I didn't know very well how to work with the observable asynchronous calls. The sequence is: - check if the token is valid. If it is valid, execute the original http request. done. - if the token is not valid, call a request for a new token, as an observable, and when you get the new token ( inside the .pipe(...) from observable ) add the token to the header and execute the original http request.
Ok, thanks! I managed to fix it by using flatMap() instead of 'map()`.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.