Angular-oauth2-oidc: Missing an HttpClient interceptor

Created on 6 Dec 2017  路  4Comments  路  Source: manfredsteyer/angular-oauth2-oidc

The switch in 3.0 to HttpClient breaks exisiting interceptors which neeeds the OAuthService and HttpClient. (Circular dependencies). This interceptors are used to automate the header settings to authenticate request.

But I did not find a configuration / helper to let the OAuthService to intercept the HTTPClient itself.

The current documentation / examples says that we have to add the headers on each single call.
And I did not find an Interceptor in angular-oath2-oidc (or I missed a point).

Most helpful comment

This is a known issue - see https://github.com/angular/angular/issues/18224

A workaround can be:

@Injectable()
export class TokenInterceptor implements HttpInterceptor {

  private authService: AuthService;

  // Would like to inject authService directly but it causes a cyclic dependency error
  // see https://github.com/angular/angular/issues/18224
  constructor(private injector: Injector) {
  }

  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {

    request = request.clone({
      setHeaders: {
        Authorization: this.getAuthService().authorizationHeader
      }
    });

    return next.handle(request);
  }

  getAuthService(): AuthService {
    if (typeof this.authService === 'undefined') {
      this.authService = this.injector.get(AuthService);
    }
    return this.authService;
  }
}

All 4 comments

I was also having issues using an interceptor with the OAuthService. For example, in my interceptor, if I receive a 401 status, I want to call "initImplicitFlow()". However, injecting the OatuhService into the Interceptor creates a circular dependency.

I was able to resolve this by using the router to redirect to a LoginComponent. The LoginComponent then injected the OAuthService and called initImplicitFlow().

Not sure if any of this helps you.

This is a known issue - see https://github.com/angular/angular/issues/18224

A workaround can be:

@Injectable()
export class TokenInterceptor implements HttpInterceptor {

  private authService: AuthService;

  // Would like to inject authService directly but it causes a cyclic dependency error
  // see https://github.com/angular/angular/issues/18224
  constructor(private injector: Injector) {
  }

  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {

    request = request.clone({
      setHeaders: {
        Authorization: this.getAuthService().authorizationHeader
      }
    });

    return next.handle(request);
  }

  getAuthService(): AuthService {
    if (typeof this.authService === 'undefined') {
      this.authService = this.injector.get(AuthService);
    }
    return this.authService;
  }
}
request = request.clone({
  setHeaders: {
    Authorization: this.getAuthService().authorizationHeader
  }
});

should be:

request = request.clone({
  setHeaders: {
    Authorization: this.getAuthService().authorizationHeader()
  }
});

as .authorizationHeader is a method. Works otherwise, thanks.

Thx for this question and those answers.

Another solution I like to prevent the circular dependency is using the OAuthStore instead of the whole service:

import { Injectable, Inject } from '@angular/core';
import { OAuthService, OAuthStorage } from 'angular-oauth2-oidc';
import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest, HttpResponse, HttpErrorResponse } from '@angular/common/http';
import {Observable} from 'rxjs/Observable';
import { BASE_URL } from '../../crud-helper/base-url.token';
import { map, catchError } from 'rxjs/operators';
import { of } from 'rxjs/observable/of';
import { Router } from '@angular/router';

@Injectable()
export class AuthInterceptor implements HttpInterceptor {

    constructor(
        private authStorage: OAuthStorage,
        @Inject(BASE_URL) private baseUrl: string,
        private router: Router) {
    }

    public intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {

        let url = req.url.toLowerCase();

        if (url.startsWith(this.baseUrl)) {

            let token = this.authStorage.getItem('access_token');
            let header = 'Bearer ' + token;

            let headers = req.headers
                                .set('Authorization', header);

            req = req.clone({ headers });
        }

        return next.handle(req).pipe(
            map(event => event),
            catchError(err => this.handleError(err))
        )
    }






    handleError(error: HttpErrorResponse) {

        console.error('error intercepted', error);

        if (error.status === 401 || error.status === 403) {
            this.router.navigate(['/home', {needsLogin: true}]);
            return of(null);
        }
        return of(error); 
    }

}

To make this work, we currently need to explicitly register an OAuthStorage, even though when we still want to stick with the default implementation used which is the Browser's sessionStorage:

  providers: [
    { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true },
    { provide: OAuthStorage, useValue: sessionStorage }
  ],

In future versions this won't be necessary anymore.

Was this page helpful?
0 / 5 - 0 ratings