10

Using Angular 6, I have tried many different approaches over the last two days, with the latest riffing off of this post: https://stackoverflow.com/a/47401544. However, the header is still not being set on requests.

import {Inject, Injectable} from '@angular/core';
import {
  HttpEvent,
  HttpInterceptor,
  HttpHandler,
  HttpRequest,
  HttpErrorResponse,
} from '@angular/common/http';
import { Observable } from 'rxjs/Observable';

@Injectable()
export class AuthTokenInterceptor implements HttpInterceptor {
  constructor() {}

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    return next.handle(req).do((event: HttpEvent<any>) => {
      if (localStorage.getItem('id_token') != null) {
        // Clone the request to add the new header.
        const request = req.clone({
          setHeaders: {
            'Content-Type' : 'application/json; charset=utf-8',
            'Accept'       : 'application/json',
            'Authorization': `Bearer ${localStorage.getItem('id_token')}`
          }
        });
        return next.handle(request);
      }
    }, (err: any) => {
      if (err instanceof HttpErrorResponse) {
        if (err.status === 401) {
          console.log('redirect auth interceptor')
          // do a redirect
        }
      }
    });
  }
}

If I log out request, the request.headers.lazyUpdate array is being updated with 3 items, but I don't see the Authorization header in the request it's intercepting.

request.headers.lazyUpdate:

{name: "Content-Type", value: "application/json; charset=utf-8", op: "s"}
{name: "Accept", value: "application/json", op: "s"}
{name: "Authorization", value: "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ2Mzh9.tLTmPK46NhXSuqoCfZKgZcrQWzlNqLMI71-G0iy3bi8", op: "s"}

(request.headers.headers is empty---could this be the problem?)

app.module.ts:

providers: [
    {provide: HTTP_INTERCEPTORS, useClass: AuthTokenInterceptor, multi: true},
  ],

What leads me to think it's an interceptor issue is that if I manually add the headers to the request, I don't get a 401 and the request returns the proper data and a 200:

return this.http.get(environment.API_URL + 'list/supervise/' + encodeURIComponent(id),
      {headers: new HttpHeaders().set('Authorization', `Bearer ${localStorage.getItem('id_token')}`)}).pipe(
        map((res: any) => res.data)
    );

Is there anything I may be overlooking? Thanks.

EDIT:

As I mention in a comment below, I was returning next.handle twice. This is the solution I ended up going with:

import {Injectable} from '@angular/core';
import {
  HttpEvent,
  HttpInterceptor,
  HttpHandler,
  HttpRequest
} from '@angular/common/http';
import { Observable } from 'rxjs';

@Injectable()
export class AuthTokenInterceptor implements HttpInterceptor {
  constructor() {}

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    const token = localStorage.getItem('id_token');

    req = req.clone({
      setHeaders: {
        'Authorization': `Bearer ${token}`
      },
    });

    return next.handle(req);
  }
}
kriskanya
  • 667
  • 2
  • 6
  • 17

4 Answers4

3

You could try a simpler version of it.(just like your reference link does)

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    const jwt = localStorage.getItem('id_token');
    if (!!jwt) {
     req = req.clone({
       setHeaders: {
         Authorization: `Bearer ${jwt}`
       }
     });
   }
   return next.handle(req);
 }

You don't have to handle the error here
since the point of intercepter here(in your context) is to clone(that means whenever we took request, we clone it, then do whatever we want and send it away).
We could add more headers more data
And it will be sent away, then eventually coming back with return from Api
And leave the handle problem to the service that call the httpRequest(eg: then, catch, pipe,...).

Again, you declared this in app.module.ts which mean all of the requestto api in your app will be intercept, and what if I want to handle a specific request with the error message Nothing here?, and if you do some complicated logic, it could affect all request.

And about your code above, I haven't try it, but I think their could be something wrong happened when you nested like that or so, you should put the break point their and tried to debug what happened.

lupa
  • 529
  • 2
  • 7
  • "You don't have to handle the error here" , but he could do, next.handle(req) returns an Observable so he can do something like next.handle(req).pipe(map(x => { ... })) – enno.void Jul 20 '18 at 17:38
  • I think I accidentally combined a few approaches and messed it up. I was mistakenly doing `return next.handle(req).do((event: HttpEvent) => {` and `return next.handle(request);`. – kriskanya Jul 20 '18 at 18:42
  • It is also important to check if the req.url is your intended API. As the token is sensitive information, you don't want to send the token to all other 3rd party URLs that your Angular app might call. – Nagesh Andani Jun 04 '21 at 13:26
3

Full solution I went with:

import {Injectable} from '@angular/core';
import {
  HttpEvent,
  HttpInterceptor,
  HttpHandler,
  HttpRequest
} from '@angular/common/http';
import { Observable } from 'rxjs';

@Injectable()
export class AuthTokenInterceptor implements HttpInterceptor {
  constructor() {}

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    const token = localStorage.getItem('id_token');

    req = req.clone({
      setHeaders: {
        'Authorization': `Bearer ${token}`
      },
    });

    return next.handle(req);
  }
}
kriskanya
  • 667
  • 2
  • 6
  • 17
1

So the first issue i see here is that u dont return if there is no value in localStorage. i would structure the interceptor like this:

export class AuthInterceptor implements HttpInterceptor {

    private APIToken = null;
    private defaultApplicationHeaders = {
        'Content-Type': 'application/json'
    }

    buildRequestHeaders():HttpHeaders {

        let headers = this.defaultApplicationHeaders;

        // set API-Token if available
        if(this.APIToken !== null) {
            let authHeaderTpl = `Bearer ${this.APIToken}`;
            headers['Authorization'] = authHeaderTpl
        }

        return new HttpHeaders(headers);
    }

    constructor() {
        this.APIToken = localStorage.getItem('id_token')
    }

    intercept(req: HttpRequest<any>, next: HttpHandler) {
        const headers = this.buildRequestHeaders();
        const authReq = req.clone({ headers });

        return next.handle(authReq);
    }
}
enno.void
  • 6,242
  • 4
  • 25
  • 42
0

For those who looks about msal-angular MsalHttpInterceptor. For now I made own implementation of MsalInterceptor.
This is my custom code block:

// #region own workaround in order not to put every api endpoint url to settings
if (!scopes && req.url.startsWith(this.settingsService.apiUrl)) {
  scopes = [this.auth.getCurrentConfiguration().auth.clientId];
}
// #endregion

// If there are no scopes set for this request, do nothing.
if (!scopes) {
  return next.handle(req);
}

PS: Vote for wildcard in protectedResourceMap https://github.com/AzureAD/microsoft-authentication-library-for-js/issues/1776

Cyclion
  • 738
  • 9
  • 9