How to handle unauthorized requests(status with 401 or 403) with new httpClient in angular 4.3

53,703

Solution 1

You should use your interceptor and just handle it like this:

@Injectable()
export class AuthInterceptor implements HttpInterceptor {
    constructor(private router: Router) { }

    private handleAuthError(err: HttpErrorResponse): Observable<any> {
        //handle your auth error or rethrow
        if (err.status === 401 || err.status === 403) {
            //navigate /delete cookies or whatever
            this.router.navigateByUrl(`/login`);
            // if you've caught / handled the error, you don't want to rethrow it unless you also want downstream consumers to have to handle it as well.
            return of(err.message); // or EMPTY may be appropriate here
        }
        return throwError(err);
    }

    intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    // Clone the request to add the new header.
        const authReq = req.clone({headers: req.headers.set(Cookie.tokenKey, Cookie.getToken())});
        // catch the error, make specific functions for catching specific errors and you can chain through them with more catch operators
        return next.handle(authReq).pipe(catchError(x=> this.handleAuthError(x))); //here use an arrow function, otherwise you may get "Cannot read property 'navigate' of undefined" on angular 4.4.2/net core 2/webpack 2.70
    }
}

no need for the http service wrapper.

to use the router you'll need a factory provider like:

 providers: [
     {
         provide: HTTP_INTERCEPTORS,
         useFactory: function(router: Router) {
           return new AuthInterceptor(router);
         },
         multi: true,
         deps: [Router]
      },
      .... other providers ...
  ]

where ever you're providing the interceptor (probably app.module). don't use an arrow function. they aren't supported in factory functions when you try to build for prod.

Working plunk: https://plnkr.co/edit/UxOEqhEHX1tCDVPDy488?p=preview

Solution 2

From the @bryan60 suggestion I made few changes to his solution

In app.module.ts:

providers: [
     {
        provide: HTTP_INTERCEPTORS,
        useFactory: function(injector: Injector) {
            return new AuthInterceptor(injector);
        },
        multi: true,
        deps: [Injector]
    },
      .... other providers ...
]

and in auth-interceptor.service.ts:

import {Injectable, Injector} from '@angular/core';
import {HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest} from '@angular/common/http';
import {Observable} from 'rxjs/Observable';
import {Cookie} from './cookie.service';
import {Router} from '@angular/router';
import {UserService} from './user.service';
import {ToasterService} from '../toaster/toaster.service';
import 'rxjs/add/operator/catch';
import 'rxjs/add/observable/throw';

@Injectable()
export class AuthInterceptor implements HttpInterceptor {
    constructor(private injector: Injector) {}

    private handleError(err: HttpErrorResponse): Observable<any> {
        let errorMsg;
        if (err.error instanceof Error) {
            // A client-side or network error occurred. Handle it accordingly.
            errorMsg = `An error occurred: ${err.error.message}`;
        } else {
            // The backend returned an unsuccessful response code.
            // The response body may contain clues as to what went wrong,
            errorMsg = `Backend returned code ${err.status}, body was: ${err.error}`;
        }
        if (err.status === 404 || err.status === 403) {
            this.injector.get(UserService).purgeAuth();
            this.injector.get(ToasterService).showError(`Unauthorized`, errorMsg);
            this.injector.get(Router).navigateByUrl(`/login`);
        }
        console.error(errorMsg);
        return Observable.throw(errorMsg);
    }

    intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        // Clone the request to add the new header.
        const authReq = req.clone({headers: req.headers.set(Cookie.tokenKey, Cookie.getToken())});
        // Pass on the cloned request instead of the original request.
        return next.handle(authReq).catch(err => this.handleError(err));
    }
}

If you are using AOT in building try:

export function authInterceptorFactory(injector: Injector) {
    return new AuthInterceptor(injector);
}

providers: [
         {
            provide: HTTP_INTERCEPTORS,
            useFactory: authInterceptorFactory,
            multi: true,
            deps: [Injector]
        },
          .... other providers ...
]

Solution 3

the above @bryan60 answer is works fine , if any one facing issue like me with catch the error in below line

return next.handle(authReq).catch(x=> this.handleAuthError(x));

using do() handle the error(if you face issue with catch())

import in file:

import 'rxjs/add/operator/do';

handle error:

return next.handle(authReq)
 .do(
    success => {/*todo*/},
    err => {this.handleAuthError(authReq)}
    );
}

handleAuthError(err: any) {
    if(err.status === 401 || err.status === 403) {
    this.storageService.clear();
    window.location.href = '/home';
    }
}

I hope this is help someone.

Share:
53,703
Sai Ram Gupta
Author by

Sai Ram Gupta

I am UI developer who is very much passionate about learning exciting things in the UI

Updated on October 24, 2020

Comments

  • Sai Ram Gupta
    Sai Ram Gupta over 3 years

    I have an auth-interceptor.service.ts to handle the requests

    import {Injectable} from '@angular/core';
    import {HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest} from '@angular/common/http';
    import {Observable} from 'rxjs/Observable';
    import {Cookie} from './cookie.service';
    import {Router} from '@angular/router';
    
    @Injectable()
    export class AuthInterceptor implements HttpInterceptor {
        constructor(private router: Router) {}
        intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
            // Clone the request to add the new header.
            const authReq = req.clone({headers: req.headers.set(Cookie.tokenKey, Cookie.getToken())});
            // Pass on the cloned request instead of the original request.
            return next.handle(authReq).catch(this.handleError);
        }
    
        private handleError(err: HttpErrorResponse): Observable<any> {
            console.log(err);
            if (err.status === 401 || err.status === 403) {
                Cookie.deleteUser();
                this.router.navigateByUrl(`/login`);
                return Observable.of(err.message);
            }
            // handle your auth error or rethrow
            return Observable.throw(err);
        }
    }
    

    But I get the following error. Nothing really happens like it doesn't delete the cookie or it doesn't navigate to login page Any help or suggestions would be appreciated.

    enter image description here