Handling 401s globally with Angular

后端 未结 8 933
误落风尘
误落风尘 2020-11-28 18:50

In my Angular 2 project I make API calls from services that return an Observable. The calling code then subscribes to this observable. For example:

getCampai         


        
相关标签:
8条回答
  • 2020-11-28 19:39

    To avoid the cyclic referencing issue that is caused by having services like "Router" being injected into an Http derived class, one must use the post-constructor Injector method. The following code is a working implementation of an Http service that redirects to Login route each time a REST API returns "Token_Expired". Note that it can be used as a substitution to the regular Http and as such, doesn't require to change anything in your application's already existing components or services.

    app.module.ts

      providers: [  
        {provide: Http, useClass: ExtendedHttpService },
        AuthService,
        PartService,
        AuthGuard
      ],

    extended-http.service.ts

    import { Injectable, Injector } from '@angular/core';
    import { Request, XHRBackend, RequestOptions, Response, Http, RequestOptionsArgs, Headers } from '@angular/http';
    import { Observable } from 'rxjs/Observable';
    import { Router } from '@angular/router';
    import { AuthService } from './auth.service';
    import 'rxjs/add/operator/catch';
    import 'rxjs/add/observable/throw';
    
    @Injectable()
    export class ExtendedHttpService extends Http {
        private router; 
        private authService;
    
      constructor(  backend: XHRBackend, defaultOptions: RequestOptions, private injector: Injector) {
        super(backend, defaultOptions);
      }
    
      request(url: string | Request, options?: RequestOptionsArgs): Observable<Response> {
     
        if (typeof url === 'string') {
          if (!options) {
            options = { headers: new Headers() };
          }
          this.setHeaders(options);
        } else {
          this.setHeaders(url);
        }
        console.log("url: " + JSON.stringify(url) +", Options:" + options);
    
        return super.request(url, options).catch(this.catchErrors());
      }
    
      private catchErrors() {
    
        return (res: Response) => {
            if (this.router == null) {
                this.router = this.injector.get(Router);
            }
            if (res.status === 401 || res.status === 403) {
                //handle authorization errors
                //in this example I am navigating to login.
                console.log("Error_Token_Expired: redirecting to login.");
                this.router.navigate(['signin']);
            }
            return Observable.throw(res);
        };
      }
    
      private setHeaders(objectToSetHeadersTo: Request | RequestOptionsArgs) {
          
          if (this.authService == null) {
                this.authService = this.injector.get(AuthService);
          }
        //add whatever header that you need to every request
        //in this example I could set the header token by using authService that I've created
         //objectToSetHeadersTo.headers.set('token', this.authService.getToken());
      }
    }

    0 讨论(0)
  • 2020-11-28 19:45

    Description

    The best solution I have found is to override the XHRBackend such that the HTTP response status 401 and 403 leads to a particular action.

    If you handle your authentication outside your Angular application you could force a refresh of the current page such that your external mechanism is triggered. I detail this solution in the implementation below.

    You could also forward to a component inside your application such that your Angular application is not reloaded.

    Implementation

    Angular > 2.3.0

    Thanks to @mrgoos, here is a simplified solution for angular 2.3.0+ due to a bug fix in angular 2.3.0 (see issue https://github.com/angular/angular/issues/11606) extending directly the Http module.

    import { Injectable } from '@angular/core';
    import { Request, XHRBackend, RequestOptions, Response, Http, RequestOptionsArgs, Headers } from '@angular/http';
    import { Observable } from 'rxjs/Observable';
    import 'rxjs/add/operator/catch';
    import 'rxjs/add/observable/throw';
    
    
    @Injectable()
    export class AuthenticatedHttpService extends Http {
    
      constructor(backend: XHRBackend, defaultOptions: RequestOptions) {
        super(backend, defaultOptions);
      }
    
      request(url: string | Request, options?: RequestOptionsArgs): Observable<Response> {
        return super.request(url, options).catch((error: Response) => {
                if ((error.status === 401 || error.status === 403) && (window.location.href.match(/\?/g) || []).length < 2) {
                    console.log('The authentication session expires or the user is not authorised. Force refresh of the current page.');
                    window.location.href = window.location.href + '?' + new Date().getMilliseconds();
                }
                return Observable.throw(error);
            });
      }
    }
    

    The module file now only contains the following provider.

    providers: [
        { provide: Http, useClass: AuthenticatedHttpService }
    ]
    

    Another solution using Router and an external authentication service is detailed in the following gist by @mrgoos.

    Angular pre-2.3.0

    The following implementation works for Angular 2.2.x FINAL and RxJS 5.0.0-beta.12.

    It redirects to the current page (plus a parameter to get a unique URL and avoid caching) if an HTTP code 401 or 403 is returned.

    import { Request, XHRBackend, BrowserXhr, ResponseOptions, XSRFStrategy, Response } from '@angular/http';
    import { Observable } from 'rxjs/Observable';
    import 'rxjs/add/operator/catch';
    import 'rxjs/add/observable/throw';
    
    export class AuthenticationConnectionBackend extends XHRBackend {
    
        constructor(_browserXhr: BrowserXhr, _baseResponseOptions: ResponseOptions, _xsrfStrategy: XSRFStrategy) {
            super(_browserXhr, _baseResponseOptions, _xsrfStrategy);
        }
    
        createConnection(request: Request) {
            let xhrConnection = super.createConnection(request);
            xhrConnection.response = xhrConnection.response.catch((error: Response) => {
                if ((error.status === 401 || error.status === 403) && (window.location.href.match(/\?/g) || []).length < 2) {
                    console.log('The authentication session expires or the user is not authorised. Force refresh of the current page.');
                    window.location.href = window.location.href + '?' + new Date().getMilliseconds();
                }
                return Observable.throw(error);
            });
            return xhrConnection;
        }
    
    }
    

    with the following module file.

    import { BrowserModule } from '@angular/platform-browser';
    import { NgModule } from '@angular/core';
    import { CommonModule } from '@angular/common';
    import { HttpModule, XHRBackend } from '@angular/http';
    import { AppComponent } from './app.component';
    import { AuthenticationConnectionBackend } from './authenticated-connection.backend';
    
    @NgModule({
        bootstrap: [AppComponent],
        declarations: [
            AppComponent,
        ],
        entryComponents: [AppComponent],
        imports: [
            BrowserModule,
            CommonModule,
            HttpModule,
        ],
        providers: [
            { provide: XHRBackend, useClass: AuthenticationConnectionBackend },
        ],
    })
    export class AppModule {
    }
    
    0 讨论(0)
提交回复
热议问题