Search code examples
javascriptangularrxjsangular-http-interceptors

next.handle(request) in multiple HTTP interceptors fails: undefined


In a project, I work with 2 HTTP interceptors: 1 to add a JWT token to each request, the other to intercept an incoming 401 error status.

I call a separate program to get all feedback for my app in this service:

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';

import { environment } from '@environments/environment';
import { Feedback } from '@app/_models/feedback';

@Injectable({ providedIn: 'root' })
export class FeedbackService {
    constructor(
        private http: HttpClient
    ) {}

    getAll() {
        return this.http.get<Feedback[]>(`${environment.apiUrl}/feedback`);
    }

    getById(id: string) {
        return this.http.get<Feedback>(`${environment.apiUrl}/feedback/${id}`);
    }

    delete(id: string) {
        return this.http.delete(`${environment.apiUrl}/feedback/${id}`);
    }
}

The JWT interceptor:

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

import { environment } from '@environments/environment';
import { AuthorizationService } from 'src/shared/authorization.service';

@Injectable()
export class JwtInterceptor implements HttpInterceptor {
    constructor(private auth: AuthorizationService) { }

    intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        // add auth header with jwt if user is logged in and request is to the api url
        const authenticatedUser = this.auth.getAuthenticatedUser();
        if (authenticatedUser == null) {
            return;
        }
        authenticatedUser.getSession( (err, session) => {
            if (err) {
                console.log(err);
                return;
            }
            const isApiUrl = request.url.startsWith(environment.apiUrl);
            const token = session.getIdToken().getJwtToken();
            const headers = new Headers();
            headers.append('Authorization', token);
            if (this.auth.isLoggedIn() && isApiUrl) {
                request = request.clone({
                    setHeaders: {
                        Authorization: token,
                    }
                });
            }

            return next.handle(request);
        });
    }
}

The Error interceptor:

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

import { AccountService } from '@app/_services';

@Injectable()
export class ErrorInterceptor implements HttpInterceptor {
    constructor(private accountService: AccountService) {}

    intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        console.log(next.handle(request));
        return next.handle(request).pipe(catchError(err => {
            if (err.status === 401) {
                // auto logout if 401 response returned from api
                this.accountService.logout();
            }

            const error = err.error.message || err.statusText;
            return throwError(error);
        }));
    }
}

When I provide both interceptors in my app.module,

{ provide: HTTP_INTERCEPTORS, useClass: ErrorInterceptor, multi: true },
{ provide: HTTP_INTERCEPTORS, useClass: JwtInterceptor, multi: true },

I always get an error saying the following below. This happens because next.handle(request) apparently is undefined, and I don't really know why. Using only the Error interceptor works with no issue.

ERROR TypeError: Cannot read property 'pipe' of undefined
    at ErrorInterceptor.intercept (error.interceptor.ts:14)
    at HttpInterceptorHandler.handle (http.js:1958)
    at HttpXsrfInterceptor.intercept (http.js:2819)
    at HttpInterceptorHandler.handle (http.js:1958)
    at HttpInterceptingHandler.handle (http.js:2895)
    at MergeMapSubscriber.project (http.js:1682)
    at MergeMapSubscriber._tryNext (mergeMap.js:46)
    at MergeMapSubscriber._next (mergeMap.js:36)
    at MergeMapSubscriber.next (Subscriber.js:49)
    at Observable._subscribe (subscribeToArray.js:3)

Using only the JwtInterceptor gives following error, which I can't figure out where it's coming from. Of course, I would want to use both. Am I missing something while configuring the multiple interceptors?

ERROR TypeError: You provided 'undefined' where a stream was expected. You can provide an Observable, Promise, Array, or Iterable.
    at subscribeTo (subscribeTo.js:27)
    at subscribeToResult (subscribeToResult.js:11)
    at MergeMapSubscriber._innerSub (mergeMap.js:59)
    at MergeMapSubscriber._tryNext (mergeMap.js:53)
    at MergeMapSubscriber._next (mergeMap.js:36)
    at MergeMapSubscriber.next (Subscriber.js:49)
    at Observable._subscribe (subscribeToArray.js:3)
    at Observable._trySubscribe (Observable.js:42)
    at Observable.subscribe (Observable.js:28)
    at MergeMapOperator.call (mergeMap.js:21)

Solution

  • Rewrite your JwtInterceptor:

    import { HttpInterceptor, HttpHandler, HttpRequest, HttpEvent } from '@angular/common/http';
    import { Injectable } from '@angular/core';
    import { Observable, from } from 'rxjs';
    
    import { environment } from '@environments/environment';
    import { AuthorizationService } from 'src/shared/authorization.service';
    
    
    @Injectable()
    export class JwtInterceptor implements HttpInterceptor {
        constructor(private auth: AuthorizationService) { }
    
        intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
            return from(this.getSessionWithAuthReq(request, next));
        }
    
        async getSessionWithAuthReq(request: HttpRequest<any>, next: HttpHandler){
            const authenticatedUser = this.auth.getAuthenticatedUser();
            
            if (authenticatedUser) {
                const authRequest:  HttpRequest<any>  = await new Promise( (resolve) => {
                    authenticatedUser.getSession( (err, session) => {
                        if (err) {
                            console.log(err);
                            // want to go on without authenticating if there is an error from getting session 
                            return resolve(request);
                        }
                        const isApiUrl = request.url.startsWith(environment.apiUrl);
                        const token = session.getIdToken().getJwtToken();
                        const headers = new Headers();
                        headers.append('Authorization', token);
                        if (this.auth.isLoggedIn() && isApiUrl) {
                            const req = request.clone({
                                setHeaders: {
                                    Authorization: token,
                                }
                            });
                            return resolve(req);
                        }
                        return resolve(request);
                    });
                });
                
                
                return next.handle(authRequest).toPromise();
            }
    
            return next.handle(request).toPromise();
        }
    }