Angular 4 Tutorial – Handling Refresh Token with New HttpInterceptor
All of the code for this post is available at github.
First, an explanation of what is happening with OAuth and the refresh token. When you login, you get an authorization token and a refresh token. The refresh token is configured with a certain timeout. So when that timeout is reached (regardless of activity) you receive a 401 (Unauthorized) error from your API call. At that point, your code must attempt to refresh the token by calling the OAuth refreshToken endpoint (with the refresh token string). It gives you back a new authorization token and a new refresh token. From then on, you use the new authorization token to make your API calls.
Obviously, the new HttpInterceptor is perfect for this scenario. It can add the Authorization header to all http calls and catch exceptions to listen for 401 errors. When the 401 error happens, it can make the refreshToken call and try again with the new authorization token.
Simple UI
The sample project I created on github is just two buttons. The first button calls one API method that will generate a 401 error. The second button calls two API methods (run in parallel) that will both generate a 401 error. It displays the ending status of the method calls on screen. The hope is that the status will be 200 and not 401.
export class AppComponent { message: string; constructor(private testService: TestService, private authService: AuthService) { } test401() { this.testService.getData() .subscribe((response: any) => { this.message = `Worked with status = ${response.status}`; }, error => this.message = `Failed with status = ${error.status}`); } test401Multiple() { let dataObservable$ = this.testService.getData(); let lookupObservable$ = this.testService.getLookup(); Observable.forkJoin(dataObservable$, lookupObservable$) .subscribe(([dataResult, lookupResult]) => { this.message = `Worked with getData status = ${dataResult.status} and getLookup status = ${lookupResult.status}`; }, error => this.message = `Failed with status = ${error.status}`); } }
AuthService
The AuthService will give us the tokens. I’m making the assumption that you already know how to call your refreshToken method on the server and store the results in session storage or however you want to cache them. Here is my example code:
import { Observable } from 'rxjs/Observable'; import 'rxjs/add/observable/of'; import 'rxjs/add/operator/delay'; export class AuthService { // Assuming this would be cached somehow from a login call. public authTokenStale: string = 'stale_auth_token'; public authTokenNew: string = 'new_auth_token'; public currentToken: string; constructor() { this.currentToken = this.authTokenStale; } getAuthToken() { return this.currentToken; } refreshToken(): Observable<string> { /* The call that goes in here will use the existing refresh token to call a method on the oAuth server (usually called refreshToken) to get a new authorization token for the API calls. */ this.currentToken = this.authTokenNew; return Observable.of(this.authTokenNew).delay(200); } }
TestService
The TestService will handle the calling of the API. I created an API here: http://docs.testerrorresponses.apiary.io/#
import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; @Injectable() export class TestService { constructor(private http: HttpClient) { } getData() { return this.http.get<{status}>('http://private-4002d-testerrorresponses.apiary-mock.com/getDataError401'); } getLookup() { return this.http.get<{status}>('http://private-4002d-testerrorresponses.apiary-mock.com/getLookupError401'); } }
- The important thing to note here is that you must use HttpClient (which is new in Angular 4.3+) imported from @angular/common/http
- The “classic” http client does not trigger the HttpInterceptor
- The API call simply returns a 401 error
HttpInterceptor
The RequestInterceptorService will implement HttpInterceptor which has only one method: intercept. It will add a token to the header on each call and catch any errors that might occur.
@Injectable() export class RequestInterceptorService implements HttpInterceptor { isRefreshingToken: boolean = false; tokenSubject: BehaviorSubject<string> = new BehaviorSubject<string>(null); constructor(private authService: AuthService) {} addToken(req: HttpRequest<any>, token: string): HttpRequest<any> { return req.clone({ setHeaders: { Authorization: 'Bearer ' + token }}) } intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpSentEvent | HttpHeaderResponse | HttpProgressEvent | HttpResponse<any> | HttpUserEvent<any>> { return next.handle(this.addToken(req, this.authService.getAuthToken())) .catch(error => { if (error instanceof HttpErrorResponse) { switch ((<HttpErrorResponse>error).status) { case 400: return this.handle400Error(error); case 401: return this.handle401Error(req, next); } } else { return Observable.throw(error); } }); }
- We’ll use isRefreshingToken and tokenSubject later
- addToken will add the Bearer token to the Authorization header
- In the intercept method, we return next.handle and pass in the cloned request with a header added
- Get the auth token from the AuthService
- In the catch, we can handle any and all errors that occur, of primary interest to this example is the 401 Unauthorized error
Handling 401 Unauthorized
The code to handle the 401 error is the most important.
handle401Error(req: 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() .switchMap((newToken: string) => { if (newToken) { this.tokenSubject.next(newToken); return next.handle(this.addToken(this.getNewRequest(req), newToken)); } // If we don't get a new token, we are in trouble so logout. return this.logoutUser(); }) .catch(error => { // If there is an exception calling 'refreshToken', bad news so logout. return this.logoutUser(); }) .finally(() => { this.isRefreshingToken = false; }); } else { return this.tokenSubject .filter(token => token != null) .take(1) .switchMap(token => { return next.handle(this.addToken(this.getNewRequest(req), token)); }); } } /* This method is only here so the example works. Do not include in your code, just use 'req' instead of 'this.getNewRequest(req)'. */ getNewRequest(req: HttpRequest<any>): HttpRequest<any> { if (req.url.indexOf('getData') > 0) { return new HttpRequest('GET', 'http://private-4002d-testerrorresponses.apiary-mock.com/getData'); } return new HttpRequest('GET', 'http://private-4002d-testerrorresponses.apiary-mock.com/getLookup'); } logoutUser() { // Route to the login page (implementation up to you) return Observable.throw(""); }
- If isRefreshingToken is false (which it is by default) we will enter the code section that calls authService.refreshToken
- Immediately set isRefreshingToken to true so no more calls come in and call refreshToken again – which we don’t want of course
- Set the tokenSubject to null so that subsequent API calls will wait until the new token has been retrieved
- Call authService.refreshToken (this is an Observable that will be returned)
- When successful, call tokenSubject.next on the new token, this will notify the API calls that came in after the refreshToken call that the new token is available and that they can now use it
- Return next.handle using the new token
- For the example, I had to change the call so that it doesn’t generate a 401 error…don’t do this in your code 🙂
- If isRefreshingToken is true, we will wait until tokenSubject has a non-null value – which means the new token is ready
- Only take 1 here to avoid returning two – which will cancel the request
- When the token is available, return the next.handle of the new request
- When the call to refreshToken completes, in the finally block, reset the isRefreshingToken to false for the next time the token needs to be refreshed
- Note that no matter which path is taken, we must return an Observable that ends up resolving to a next.handle call so that the original call is matched with the altered call
Results
If I run the application and click the first button, this is what I see for output on screen:
Worked with status = 200
Then the second button, this is the output:
Worked with getData status = 200 and getLookup status = 200
The network tab will be the most interesting thing to see though because it shows a 401 error for all of the initial calls and then the refreshToken call, followed by all of the calls succeeding with 200. This is the output for the multiple calls button:
- The first two getLookup and getData calls that give a 200 status are the OPTION calls
- The second two getLookup and getData calls are the actual successful API GET calls
- The reason there is no “refreshToken” call here is that I’m simulating it and not making an actual call
- But in the wild, you should see that right after the 401 error(s)
What if the Refresh Token Times Out?
Some may be wondering at this point what would happen if the refresh token times out. Usually caused by not making any API calls for whatever the timeout is configured for. Well, what happens is that you should see a 400 error with an ‘invalid_grant’ message. So the handle400Error code should most likely log out the user and direct them to the login page.
Here is the code that checks for this situation:
handle400Error(error) { if (error && error.status === 400 && error.error && error.error.error === 'invalid_grant') { // If we get a 400 and the error message is 'invalid_grant', the token is no longer valid so logout. return this.logoutUser(); } return Observable.throw(error); }
HttpClient Caveat
After publishing this article, many of you ran into a circular dependency issue when you tried to inject AuthService into the HttpInterceptor constructor. This was because you added a dependency on HttpClient for the refreshToken call. It turns out that in the HttpInterceptor, you cannot inject anything that has a dependence on HttpClient into the constructor. So to get around it, you simply need to inject the Injector Angular class into the HttpInterceptor constructor and then use that in a JIT way within the injector.
For example, when you want to do the refreshToken call using authService, do the following before the call:
let authService = this.injector.get(AuthService);
…and then proceed to use authService instead of injecting AuthService into the constructor.
We found we had to do this with the Angular Router class as well. Hope this helps!
Conclusion
The new HttpInterceptor is a new feature in Angular 4.3+ that is very powerful and useful in all applications. As you can see, it nicely handles the OAuth refresh token seamlessly “behind the scenes” so that to the user, everything works smoothly.
Exactly what I was looking for. It works like charm. Thank you very much.
Awesome! Glad it worked for you!
How would you make a http request in refreshToken() in authservice? You can’t use DI httpclient because then you’ll get a circular dependency?
You shouldn’t get a circular dependency. I’m importing AuthService in the interceptor and importing HttpClient in the AuthService. Which is fine. Maybe you are confusing HttpInterceptor with HttpClient?
AuthService < HttpClient < HttpInterceptor < AuthService < HttpClient….
The other stuff is GREAT though thank you!! 😀 Im using Injector to get past the circular dependency
Are you providing HttpInterceptor in the AuthService? It should only be provided in the AppModule (or whatever module but only once)
Nope, but Im guessing HttpClient is providing HttpInterceptor internally? Im only providing HttpInterceptor in the AppModule like you do above 🙂
Do you have a fix for this Issue ?
The fix for this issue is to (in the interceptor service) not inject any service that has a dependency on HttpClient in the constructor. So what you do instead is use Injector in the interceptor service when you need the service that has a dependency on HttpClient. So like this: let serviceWithHttpClientDependency = this.interceptor.get(ServiceWithHttpClientDependency);
Use Injector: https://gist.github.com/tobbbe/08e01db92c32346b226333a3f952df22
on refresh token failure its never going to catch block. How can I handle that 401 error in refresh token.
We didn’t need to insert the token for our refresh token call – it is anonymous.
We do require a token for refresh token. On refresh token call failure which is 401 error the code is not able to catch because of this logout is never being called. For reference added the request interceptor, https://plnkr.co/edit/7ASmrALeawlgXWU0EWMq?p=preview
Yes, I was having the same question/problem. To get a new access token from the request token I need to make a http request and if I try using HttpClient I get a circular dependency error.
Have you fixed this ? I am facing the same issue .
@disqus_NhveFnvSHV:disqus @markozorman:disqus you could use Injector to get HttpClient:
const http = this.injector.get(HttpClient);
http.get(`/auth/refreshtoken).subscribe()
Do not inject HttpClient in the constructor
did I miss some config or something? the intercept seems to be called again on the refresh token call thus adding the expired token and failing the request.
In our implementation, the refreshToken call allows anonymous calls so it doesn’t need the auth-token.
Cannot instantiate cyclic dependency! InjectionToken_HTTP_INTERCEPTORS (“[ERROR ->]”): in NgModule AppModule in ./AppModule@-1:-1 ; Zone: ; Task: Promise.then ; Value: Error: Provider parse errors:
Error while using HttpClient in Auth service to get the refresh token.
How you bypass this issue ? Error: Provider parse errors:
Cannot instantiate cyclic dependency! InjectionToken_HTTP_INTERCEPTORS (“[ERROR ->]”): in NgModule AppModule in ./AppModule@-1:-1
I know the code is great, but how you guys solved InjectionToken_HTTP_INTERCEPTORS (“[ERROR ->]”): in NgModule AppModule in ./AppModule@-1:-1, I’ve tried using injector inside the interceptor but still got this error :((((
Finally solved ! @richfranzmeier:disqus
thank you so much man! I’ve been trying to solve it for 3 days =) Your code is EXACTLY what I needed. All the best to you =)
Awesome, thanks for the feedback!
See the updated article regarding the circular dependencies. It’s right before the conclusion section.
How do I cancel the origin req when refresh token req fails? it seems as it is impossible. I need it cause I have a guard waiting for the original req to return and it makes my app stuck. thanks
works great, but what if user reload the page when his token already expired? interceptor doesn’t work in that case
how to handle token refresh failure? On error it’s never go to catch block!
// If there is an exception calling ‘refreshToken’, bad news so logout
this block never reached, when refreshToken respond with error, neither switchMap/catch/finally reached
@intertech-c51ce410c124a10e0db5e4b97fc2af39:disqus What If refresh token timed out and returned 401 instead of 400. With the given implementation its not catching the 401 error on refresh token time out.
Hi,
Nice effort, Please you share how to handle last request after refresh token and pull out from circular dependencies and have you any example or demo project please share. Thanks
How would this work for other types of requests that aren’t GET requests?
It should work the same way as GET requests. Are you seeing something different?
the refresh token code is not there in the function when token expired .
Guyzz my problem is when there is only 1 service call it works very smooth. But i have couple of scene where i have 2 independent component who have 2 individual service to call. So when 1st call goes to service and gets 401 access_token error it dont wait for the refresh token service accomplish, the 2nd call also initiate goes to server and gets gets 401 access_token error. Please advise
Great great article!!!
But, If the refreshToken() failed, the catch() and finally() are never reach!! How to fix that!?