In this article I will describe how to add a Http Authentication Bearer token to each request done from Angular via HttpClient by implementing a Angular 5 HttpInterceptor. This way the bearer token has not be added to each request separately while doing Ajax request e.g. to a REST api. This is for example useful, if you have some api that is protected by OAuth and you have to sent a JWT token in order to get access.
HttpInterceptor:
Here is the code for the HttpInterceptor itself. It has to implement the HttpInterceptor interface and therefore provide an implementation for the intercept method.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
import { Observable } from 'rxjs/Observable'; import { Location } from '@angular/common'; import { Injectable } from '@angular/core'; import { HttpInterceptor } from '@angular/common/http'; import { HttpRequest } from '@angular/common/http'; import { HttpHandler } from '@angular/common/http'; import { HttpEvent } from '@angular/common/http'; import { MsalService } from '../services/msal.service'; import { HttpHeaders } from '@angular/common/http'; import 'rxjs/add/observable/fromPromise'; @Injectable() export class CustomHttpInterceptor implements HttpInterceptor { constructor(private msalService: MsalService) {} intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { return Observable.fromPromise(this.handleAccess(request, next)); } private async handleAccess(request: HttpRequest<any>, next: HttpHandler): Promise<HttpEvent<any>> { const token = await this.msalService.getAccessToken(); let changedRequest = request; // HttpHeader object immutable - copy values const headerSettings: {[name: string]: string | string[]; } = {}; for (const key of request.headers.keys()) { headerSettings[key] = request.headers.getAll(key); } if (token) { headerSettings['Authorization'] = 'Bearer ' + token; } headerSettings['Content-Type'] = 'application/json'; const newHeader = new HttpHeaders(headerSettings); changedRequest = request.clone({ headers: newHeader}); return next.handle(changedRequest).toPromise(); } } |
A couple of things to notice here. Since I’m working with async / await and I use Promises in my code and the intercept method returns an Observable, I have to convert my Promise to an Observable using the Observable.fromPromise method.
In the handleAccess method I get my access token from my msalService. This is a service for handling login / access etc. using Microsoft Authentication Library for JavaScript (In my case Azure AD B2C).
The next step is to actually add the token to the HttpClient request.
Immutable Request / RequestHeader
One very important point is, that you cannot simply set or add new headers to the request object.
The request and header objects are immutable. Even if you change something by adding a new header or trying to set some header, this will not work. This took me a while to figure out, because the api of these objects allows you to do so. But the changes you make are then not reflected in the requests you make later on.
So the solution is to copy / clone the original objects and do the changes there (see code above)
Add CustomHttpInterceptor to @NgModule
So the only thing left to do is to register the new CustomHttpInterceptor, so that it will be used from now on.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
import { HTTP_INTERCEPTORS } from '@angular/common/http'; @NgModule({ ... providers: [ ... { provide: HTTP_INTERCEPTORS, useClass: CustomHttpInterceptor, multi: true }, ... }) |
That’s it!
So this article demonstrates how to add a custom authorization header to all HttpClient request in Angular 5. Similarly you could e.g. add code to do a redirect to a login page here in case you get a 401 (unauthorized) from the REST api.
I hope this article helps some people save some time I wasted because of the immutable object request header.
If you have any questions etc…. leave a comment below.
You Saved My Days Thank you
Hello,
I am thankful for your tutorial and would like to ask if i could ask a specific question about oauth and angular 5. I am very new to this aspect of web applications and am a bit confused.
what is the msal service?
msal service is service in which you get, post or maybe put your web api…….i think so!!!!!!!!!
I called the services msal service, because in my case it uses the Microsoft Authentication Library for JavaScript (MSAL.js).
This is meant as an example for an angular service which is responsible for your authentication and gets your access token.
In my case this is done using Microsoft Authentication Library for JavaScript (https://github.com/AzureAD/microsoft-authentication-library-for-js).
That
for (const key of request.headers.keys())
could be afor (const key in request.headers)
.Could you show what is in the method this.msalService.getAccessToken? I’m confused how to get the token after you login through msal -> Azure AD
For me this method simply calls
return await this.clientApplication.acquireTokenSilent(this.tenantConfig.b2cScopes);
with
clientApplication = new UserAgentApplication(…)
This UserAgentApplication is from the msal library.
Basically how this is works is the following:
original page -> redirect to microsoft login page (with a provided redirect url back to your “original page” e.g. original_page/authentication) – after login microsoft will redirect you back to that page with a lot of appended url parameters.
You have to make sure that the msal library intercepts the parameters of that url. In these parameters there will be your access token. The library will store those for you. All subsequent calls to acquireTokenSilent will return that token for you instead of doing a login again.
Hope this helps. More details would probably require a separate blog article 🙂
Thank you for your reply! 🙂
Do you maybe have a link to your git repo I could look at as example?
Unfortunately the code was developed for a client and I cannot share it 🙁
I get this error Property ‘getAccessToken’ does not exist on type ‘MsalService’ when I call msalService.getAccessToken() method. I am using angular 8.
MsalService is a service I created on top of Microsoft’s MSAL library, so unless you also created one, you of course cannot have it… 🙂
Hi,
Is that can ensure this.handleAccess(request, next) before return? i used similar way while it always return before handleAccess() finished.
Hi,
Is that can ensure this.handleAccess(request, next) before return? i used similar way while it always return before handleAccess() finished.
And it’s better to share the code about this.msalService.getAccessToken(), i want to double check if there is any differ from your service.
My accessToken() is as below:
js
getToken(): Promise{
return new Promise((resolve, reject)=>{
this.commonService.getToken().subscribe(token=>{
this.localDate = new Date();
localStorage.setItem('token', JSON.stringify(token));
resolve(token);
})
})
}
what is the common service here? how are you getting token??
how to handle when token get expired.
What to do if your token expires totally depends on your authentication method. Sometimes you have 2 different tokens, one of them being a refresh-token. You could then call the API to get a new second token to be authenticated again. Another option would be to do a redirect to a login page.
If we want to access this token inside webapi .. then how can it be possible, pls help
Inside the webapi you have to either parse the header in the request yourself and do authentication or you have some authentication middleware that takes care of the whole authentication itself.
Thanks, your post helped me think even on 2021 hahaha
I have no words to say thanks to you. I have been trying this interceptor thing for three days, and this was the only working solution.
*****
small correction for users who use rxjs 6+
——————————————————————–
import { from } from ‘rxjs’;
…….
intercept(request: HttpRequest, next: HttpHandler): Observable<HttpEvent> {
var observableFromPromise = from(this.handleAccess(request, next));
return observableFromPromise;
//Observable.fromPromise may not be working in latest versions
}