Angular 4 Interceptor-Wiederholungsanforderungen nach Token-Aktualisierung

84

Hallo, ich versuche herauszufinden, wie die neuen Winkelabfangjäger implementiert und 401 unauthorizedFehler behandelt werden, indem das Token aktualisiert und die Anforderung erneut versucht wird. Dies ist der Leitfaden, dem ich gefolgt bin: https://ryanchenkie.com/angular-authentication-using-the-http-client-and-http-interceptors

Ich kann die fehlgeschlagenen Anforderungen erfolgreich zwischenspeichern und kann das Token aktualisieren, aber ich kann nicht herausfinden, wie die zuvor fehlgeschlagenen Anforderungen erneut gesendet werden. Ich möchte auch, dass dies mit den Resolvern funktioniert, die ich derzeit verwende.

token.interceptor.ts

return next.handle( request ).do(( event: HttpEvent<any> ) => {
        if ( event instanceof HttpResponse ) {
            // do stuff with response if you want
        }
    }, ( err: any ) => {
        if ( err instanceof HttpErrorResponse ) {
            if ( err.status === 401 ) {
                console.log( err );
                this.auth.collectFailedRequest( request );
                this.auth.refreshToken().subscribe( resp => {
                    if ( !resp ) {
                        console.log( "Invalid" );
                    } else {
                        this.auth.retryFailedRequests();
                    }
                } );

            }
        }
    } );

authentication.service.ts

cachedRequests: Array<HttpRequest<any>> = [];

public collectFailedRequest ( request ): void {
    this.cachedRequests.push( request );
}

public retryFailedRequests (): void {
    // retry the requests. this method can
    // be called after the token is refreshed
    this.cachedRequests.forEach( request => {
        request = request.clone( {
            setHeaders: {
                Accept: 'application/json',
                'Content-Type': 'application/json',
                Authorization: `Bearer ${ this.getToken() }`
            }
        } );
        //??What to do here
    } );
}

Die obige Datei retryFailedRequests () kann ich nicht herausfinden. Wie sende ich die Anforderungen erneut und stelle sie nach dem erneuten Versuch der Route durch den Resolver zur Verfügung?

Dies ist der gesamte relevante Code, wenn dies hilfreich ist: https://gist.github.com/joshharms/00d8159900897dc5bed45757e30405f9

Kovaci
quelle
3
Ich habe das gleiche Problem und es scheint keine Antwort zu geben.
LastTribunal

Antworten:

98

Meine endgültige Lösung. Funktioniert mit parallelen Anfragen.

UPDATE: Der mit Angular 9 / RxJS 6 aktualisierte Code, Fehlerbehandlung und Fix-Looping, wenn refreshToken fehlschlägt

import { HttpRequest, HttpHandler, HttpInterceptor, HTTP_INTERCEPTORS } from "@angular/common/http";
import { Injector } from "@angular/core";
import { Router } from "@angular/router";
import { Subject, Observable, throwError } from "rxjs";
import { catchError, switchMap, tap} from "rxjs/operators";
import { AuthService } from "./auth.service";

export class AuthInterceptor implements HttpInterceptor {

    authService;
    refreshTokenInProgress = false;

    tokenRefreshedSource = new Subject();
    tokenRefreshed$ = this.tokenRefreshedSource.asObservable();

    constructor(private injector: Injector, private router: Router) {}

    addAuthHeader(request) {
        const authHeader = this.authService.getAuthorizationHeader();
        if (authHeader) {
            return request.clone({
                setHeaders: {
                    "Authorization": authHeader
                }
            });
        }
        return request;
    }

    refreshToken(): Observable<any> {
        if (this.refreshTokenInProgress) {
            return new Observable(observer => {
                this.tokenRefreshed$.subscribe(() => {
                    observer.next();
                    observer.complete();
                });
            });
        } else {
            this.refreshTokenInProgress = true;

            return this.authService.refreshToken().pipe(
                tap(() => {
                    this.refreshTokenInProgress = false;
                    this.tokenRefreshedSource.next();
                }),
                catchError(() => {
                    this.refreshTokenInProgress = false;
                    this.logout();
                }));
        }
    }

    logout() {
        this.authService.logout();
        this.router.navigate(["login"]);
    }

    handleResponseError(error, request?, next?) {
        // Business error
        if (error.status === 400) {
            // Show message
        }

        // Invalid token error
        else if (error.status === 401) {
            return this.refreshToken().pipe(
                switchMap(() => {
                    request = this.addAuthHeader(request);
                    return next.handle(request);
                }),
                catchError(e => {
                    if (e.status !== 401) {
                        return this.handleResponseError(e);
                    } else {
                        this.logout();
                    }
                }));
        }

        // Access denied error
        else if (error.status === 403) {
            // Show message
            // Logout
            this.logout();
        }

        // Server error
        else if (error.status === 500) {
            // Show message
        }

        // Maintenance error
        else if (error.status === 503) {
            // Show message
            // Redirect to the maintenance page
        }

        return throwError(error);
    }

    intercept(request: HttpRequest<any>, next: HttpHandler): Observable<any> {
        this.authService = this.injector.get(AuthService);

        // Handle request
        request = this.addAuthHeader(request);

        // Handle response
        return next.handle(request).pipe(catchError(error => {
            return this.handleResponseError(error, request, next);
        }));
    }
}

export const AuthInterceptorProvider = {
    provide: HTTP_INTERCEPTORS,
    useClass: AuthInterceptor,
    multi: true
};
Andrei Ostrovski
quelle
3
@AndreiOstrovski, könnten Sie bitte die Antwort mit importsund den Code des AuthService aktualisieren?
Takeshin
4
Ich habe das Gefühl, dass alle parallelen Abfragen, die auf eine Aktualisierung warten, für immer warten, wenn this.authService.refreshToken () aus irgendeinem Grund fehlschlägt.
Maksim Gumerov
2
Der Haken am Aktualisierungstoken fordert mich nie an. Es traf den beobachtbaren Wurf.
Jamesmpw
2
Leute, es funktioniert mit parallelen und sequentiellen Anfragen. Sie senden 5 Anfragen, sie geben 401 zurück, dann wird 1 refreshToken ausgeführt und 5 Anfragen erneut. Wenn Ihre 5 Anfragen sequentiell sind, senden wir nach dem ersten 401 refreshToken, dann die erste Anfrage erneut und weitere 4 Anfragen.
Andrei Ostrovski
2
Warum injizieren Sie einen Service manuell, wenn Angular dies für Sie tun könnte, wenn Sie ihn dekorieren würden @Injectable()? Auch ein catchError gibt nichts zurück. Zumindest zurück EMPTY.
Győri Sándor
16

Mit der neuesten Version von Angular (7.0.0) und rxjs (6.3.3) habe ich auf diese Weise einen voll funktionsfähigen Interceptor für die automatische Sitzungswiederherstellung erstellt, der sicherstellt, dass bei gleichzeitig fehlgeschlagenen Anforderungen mit 401 auch nur die Token-Aktualisierungs-API getroffen werden sollte einmal und leiten Sie die fehlgeschlagenen Anforderungen mit switchMap und Subject an die Antwort weiter. Unten sehen Sie, wie mein Interceptor-Code aussieht. Ich habe den Code für meinen Authentifizierungsdienst und meinen Speicherdienst weggelassen, da es sich um Standarddienstklassen handelt.

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

import { AuthService } from "../auth/auth.service";
import { STATUS_CODE } from "../error-code";
import { UserSessionStoreService as StoreService } from "../store/user-session-store.service";

@Injectable()
export class SessionRecoveryInterceptor implements HttpInterceptor {
  constructor(
    private readonly store: StoreService,
    private readonly sessionService: AuthService
  ) {}

  private _refreshSubject: Subject<any> = new Subject<any>();

  private _ifTokenExpired() {
    this._refreshSubject.subscribe({
      complete: () => {
        this._refreshSubject = new Subject<any>();
      }
    });
    if (this._refreshSubject.observers.length === 1) {
      this.sessionService.refreshToken().subscribe(this._refreshSubject);
    }
    return this._refreshSubject;
  }

  private _checkTokenExpiryErr(error: HttpErrorResponse): boolean {
    return (
      error.status &&
      error.status === STATUS_CODE.UNAUTHORIZED &&
      error.error.message === "TokenExpired"
    );
  }

  intercept(
    req: HttpRequest<any>,
    next: HttpHandler
  ): Observable<HttpEvent<any>> {
    if (req.url.endsWith("/logout") || req.url.endsWith("/token-refresh")) {
      return next.handle(req);
    } else {
      return next.handle(req).pipe(
        catchError((error, caught) => {
          if (error instanceof HttpErrorResponse) {
            if (this._checkTokenExpiryErr(error)) {
              return this._ifTokenExpired().pipe(
                switchMap(() => {
                  return next.handle(this.updateHeader(req));
                })
              );
            } else {
              return throwError(error);
            }
          }
          return caught;
        })
      );
    }
  }

  updateHeader(req) {
    const authToken = this.store.getAccessToken();
    req = req.clone({
      headers: req.headers.set("Authorization", `Bearer ${authToken}`)
    });
    return req;
  }
}

Laut @ anton-toshik-Kommentar hielt ich es für eine gute Idee, die Funktionsweise dieses Codes in einem Artikel zu erläutern. Sie können meinen Artikel hier lesen diesen Code zu erklären und zu verstehen (wie und warum funktioniert er?). Ich hoffe es hilft.

Samarpan
quelle
1
Gute Arbeit, die zweite returnin der interceptFunktion sollte so aussehen : return next.handle(this.updateHeader(req)).pipe(. Derzeit senden Sie das Authentifizierungstoken erst nach dem Aktualisieren ...
Malimo
Ich glaube, ich mache das per Switchmap. Bitte überprüfe es nocheinmal. Lassen Sie mich wissen, wenn ich Ihren Standpunkt falsch verstanden habe.
Samarpan
Ja, es funktioniert im Grunde, aber Sie senden die Anfrage immer zweimal - einmal ohne den Header und dann, nachdem sie mit dem Header fehlgeschlagen ist ...
Malimo
@ SamarpanBhattacharya Das funktioniert. Ich denke, diese Antwort könnte eine Erklärung mit Semantik für jemanden wie mich vertragen, der nicht versteht, wie Observable funktioniert.
Anton Toshik
1
@NikaKurashvili, Diese Methodendefinition hat bei mir funktioniert:public refreshToken(){const url:string=environment.apiUrl+API_ENDPOINTS.REFRESH_TOKEN;const req:any={token:this.getAuthToken()};const head={};const header={headers:newHttpHeaders(head)};return this.http.post(url,req,header).pipe(map(resp=>{const actualToken:string=resp['data'];if(actualToken){this.setLocalStorage('authToken',actualToken);}return resp;}));}
Shrinivas
9

Ich bin auch auf ein ähnliches Problem gestoßen und denke, dass die Logik zum Sammeln / Wiederholen zu kompliziert ist. Stattdessen können wir einfach den catch-Operator verwenden, um nach dem 401 zu suchen, dann auf die Token-Aktualisierung zu achten und die Anforderung erneut auszuführen:

return next.handle(this.applyCredentials(req))
  .catch((error, caught) => {
    if (!this.isAuthError(error)) {
      throw error;
    }
    return this.auth.refreshToken().first().flatMap((resp) => {
      if (!resp) {
        throw error;
      }
      return next.handle(this.applyCredentials(req));
    });
  }) as any;

...

private isAuthError(error: any): boolean {
  return error instanceof HttpErrorResponse && error.status === 401;
}
rdukeshier
quelle
1
Ich mag es, einen benutzerdefinierten Statuscode von 498 zu verwenden, um ein abgelaufenes Token gegenüber 401 zu identifizieren, was auch darauf hinweisen kann, dass nicht genug priv
Joseph Carroll
1
Hallo, ich versuche, return next.handle (reqClode) zu verwenden und tue nichts. Mein Code unterscheidet sich von Ihrem Abit, aber der Teil, der nicht funktioniert, ist Return-Teil. authService.createToken (authToken, refreshToken); this.inflightAuthRequest = null; return next.handle (req.clone ({headers: req.headers.set (appGlobals.AUTH_TOKEN_KEY, authToken)}));
6
Die Erfassungs- / Wiederholungslogik ist nicht allzu kompliziert. Sie müssen dies tun, wenn Sie nicht mehrere Anforderungen an den refreshToken-Endpunkt stellen möchten, während Ihr Token abgelaufen ist. Angenommen, Ihr Token ist abgelaufen und Sie stellen fast gleichzeitig 5 Anfragen. Mit der Logik in diesem Kommentar werden serverseitig 5 neue Aktualisierungstoken generiert.
Marius Lazar
4
@JosephCarroll normalerweise nicht genug Privilegien ist 403
andrea.spot.
8

Die endgültige Lösung von Andrei Ostrovski funktioniert sehr gut, funktioniert jedoch nicht, wenn das Aktualisierungstoken ebenfalls abgelaufen ist (vorausgesetzt, Sie führen einen API-Aufruf zum Aktualisieren durch). Nach einigem Graben stellte ich fest, dass der API-Aufruf des Aktualisierungstokens auch vom Interceptor abgefangen wurde. Ich musste eine if-Anweisung hinzufügen, um dies zu handhaben.

 intercept( request: HttpRequest<any>, next: HttpHandler ):Observable<any> {
   this.authService = this.injector.get( AuthenticationService );
   request = this.addAuthHeader(request);

   return next.handle( request ).catch( error => {
     if ( error.status === 401 ) {

     // The refreshToken api failure is also caught so we need to handle it here
       if (error.url === environment.api_url + '/refresh') {
         this.refreshTokenHasFailed = true;
         this.authService.logout();
         return Observable.throw( error );
       }

       return this.refreshAccessToken()
         .switchMap( () => {
           request = this.addAuthHeader( request );
           return next.handle( request );
         })
         .catch((err) => {
           this.refreshTokenHasFailed = true;
           this.authService.logout();
           return Observable.throw( err );
         });
     }

     return Observable.throw( error );
   });
 }
James Lieu
quelle
Könnten Sie zeigen, wo Sie sonst noch mit dem refreshTokenHasFailedBooleschen Mitglied spielen?
Stephane
1
Sie finden es in der obigen Lösung von Andrei Ostrovski. Ich habe das im Grunde genommen verwendet, aber die if-Anweisung hinzugefügt, die behandelt werden soll, wenn der Aktualisierungsendpunkt abgefangen wird.
James Lieu
Dies macht keinen Sinn. Warum sollte die Aktualisierung einen 401 zurückgeben? Der Punkt ist, dass die Aktualisierung aufgerufen wird, nachdem die Authentifizierung fehlgeschlagen ist, sodass Ihre Aktualisierungs-API überhaupt nicht authentifiziert werden sollte und keine 401 zurückgeben sollte.
MDave
Aktualisierungstoken können Ablaufdaten haben. In unserem Anwendungsfall wurde festgelegt, dass es nach 4 Stunden abläuft. Wenn der Benutzer seinen Browser am Ende des Tages schließen und am nächsten Morgen zurückkehren würde, wäre das Aktualisierungstoken zu diesem Zeitpunkt abgelaufen, und daher mussten sie sich anmelden wieder rein. Wenn Ihr Aktualisierungstoken nicht abgelaufen wäre, müssten Sie diese Logik natürlich nicht anwenden
James Lieu,
4

Anhand dieses Beispiels ist hier mein Stück

@Injectable({
    providedIn: 'root'
})
export class AuthInterceptor implements HttpInterceptor {

    constructor(private loginService: LoginService) { }

    /**
     * Intercept request to authorize request with oauth service.
     * @param req original request
     * @param next next
     */
    intercept(req: HttpRequest<any>, next: HttpHandler): Observable<any> {
        const self = this;

        if (self.checkUrl(req)) {
            // Authorization handler observable
            const authHandle = defer(() => {
                // Add authorization to request
                const authorizedReq = req.clone({
                    headers: req.headers.set('Authorization', self.loginService.getAccessToken()
                });
                // Execute
                return next.handle(authorizedReq);
            });

            return authHandle.pipe(
                catchError((requestError, retryRequest) => {
                    if (requestError instanceof HttpErrorResponse && requestError.status === 401) {
                        if (self.loginService.isRememberMe()) {
                            // Authrozation failed, retry if user have `refresh_token` (remember me).
                            return from(self.loginService.refreshToken()).pipe(
                                catchError((refreshTokenError) => {
                                    // Refresh token failed, logout
                                    self.loginService.invalidateSession();
                                    // Emit UserSessionExpiredError
                                    return throwError(new UserSessionExpiredError('refresh_token failed'));
                                }),
                                mergeMap(() => retryRequest)
                            );
                        } else {
                            // Access token failed, logout
                            self.loginService.invalidateSession();
                            // Emit UserSessionExpiredError
                            return throwError(new UserSessionExpiredError('refresh_token failed')); 
                        }
                    } else {
                        // Re-throw response error
                        return throwError(requestError);
                    }
                })
            );
        } else {
            return next.handle(req);
        }
    }

    /**
     * Check if request is required authentication.
     * @param req request
     */
    private checkUrl(req: HttpRequest<any>) {
        // Your logic to check if the request need authorization.
        return true;
    }
}

Möglicherweise möchten Sie überprüfen, ob der Benutzer aktiviert ist Remember Me Verwendung des Aktualisierungstokens für einen erneuten Versuch aktiviert hat oder nur zur Abmeldeseite umleitet.

Zu Ihrer Information, das LoginServicehat die folgenden Methoden:
- getAccessToken (): string - return the current access_token
- isRememberMe (): boolean - prüfe, ob der Benutzer refresh_token
- refreshToken (): Observable / Promise - Request to oauth server for new access_tokenusing refresh_token
- invalidateSession (): ungültig - Entfernen Sie alle Benutzerinformationen und leiten Sie zur Abmeldeseite weiter

Thanh Nhan
quelle
Haben Sie ein Problem mit mehreren Anforderungen, die mehrere Aktualisierungsanforderungen senden?
CodingGorilla
Diese Version gefällt mir am besten, aber ich habe ein Problem, bei dem meine eine Anfrage stellt, wenn die Rückgabe 401 versucht, aktualisiert zu werden, wenn diese einen Fehler zurückgibt, versucht sie ständig, die Anfrage erneut zu senden, ohne anzuhalten. Mache ich etwas falsch?
Jamesmpw
Entschuldigung, der vorher habe ich nicht sorgfältig getestet. Ich habe gerade meinen Beitrag mit dem getesteten bearbeitet, den ich verwende (auch auf rxjs6 migrieren und Token neu aktualisieren, URL überprüfen).
Thanh Nhan
1

Idealerweise möchten Sie überprüfen isTokenExpired bevor die Anfrage gesendet wird. Und wenn abgelaufen, aktualisieren Sie das Token und fügen Sie aktualisiert im Header hinzu.

Davon abgesehen retry operator kann dies bei Ihrer Logik zum Aktualisieren des Tokens bei der 401-Antwort hilfreich sein.

Verwenden Sie das RxJS retry operatorin Ihrem Dienst, wo Sie eine Anfrage stellen. Es akzeptiert aretryCount Argument. Wenn nicht angegeben, wird die Sequenz auf unbestimmte Zeit wiederholt.

Aktualisieren Sie in Ihrem Interceptor bei Antwort das Token und geben Sie den Fehler zurück. Wenn Ihr Dienst den Fehler zurückerhält, aber jetzt ein Wiederholungsoperator verwendet wird, wird die Anforderung wiederholt, und diesmal mit dem aktualisierten Token (Interceptor verwendet ein aktualisiertes Token, um den Header hinzuzufügen.)

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

@Injectable()
export class YourService {

  constructor(private http: HttpClient) {}

  search(params: any) {
    let tryCount = 0;
    return this.http.post('https://abcdYourApiUrl.com/search', params)
      .retry(2);
  }
}
Lahar Shah
quelle
0
To support ES6 syntax the solution needs to be bit modify and that is as following also included te loader handler on multiple request


        private refreshTokenInProgress = false;
        private activeRequests = 0;
        private tokenRefreshedSource = new Subject();
        private tokenRefreshed$ = this.tokenRefreshedSource.asObservable();
        private subscribedObservable$: Subscription = new Subscription();



 intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        if (this.activeRequests === 0) {
            this.loaderService.loadLoader.next(true);
        }
        this.activeRequests++;

        // Handle request
        request = this.addAuthHeader(request);

        // NOTE: if the flag is true it will execute retry auth token mechanism ie. by using refresh token it will fetch new auth token and will retry failed api with new token
        if (environment.retryAuthTokenMechanism) {
            // Handle response
            return next.handle(request).pipe(
                catchError(error => {
                    if (this.authenticationService.refreshShouldHappen(error)) {
                        return this.refreshToken().pipe(
                            switchMap(() => {
                                request = this.addAuthHeader(request);
                                return next.handle(request);
                            }),
                            catchError(() => {
                                this.authenticationService.setInterruptedUrl(this.router.url);
                                this.logout();
                                return EMPTY;
                            })
                        );
                    }

                    return EMPTY;
                }),
                finalize(() => {
                    this.hideLoader();
                })
            );
        } else {
            return next.handle(request).pipe(
                catchError(() => {
                    this.logout();
                    return EMPTY;
                }),
                finalize(() => {
                    this.hideLoader();
                })
            );
        }
    }

    ngOnDestroy(): void {
        this.subscribedObservable$.unsubscribe();
    }

    /**
     * @description Hides loader when all request gets complete
     */
    private hideLoader() {
        this.activeRequests--;
        if (this.activeRequests === 0) {
            this.loaderService.loadLoader.next(false);
        }
    }

    /**
     * @description set new auth token by existing refresh token
     */
    private refreshToken() {
        if (this.refreshTokenInProgress) {
            return new Observable(observer => {
                this.subscribedObservable$.add(
                    this.tokenRefreshed$.subscribe(() => {
                        observer.next();
                        observer.complete();
                    })
                );
            });
        } else {
            this.refreshTokenInProgress = true;

            return this.authenticationService.getNewAccessTokenByRefreshToken().pipe(tap(newAuthToken => {
            this.authenticationService.updateAccessToken(newAuthToken.access_token);
            this.refreshTokenInProgress = false;
            this.tokenRefreshedSource.next();
        }));
        }
    }

    private addAuthHeader(request: HttpRequest<any>) {
        const accessToken = this.authenticationService.getAccessTokenOnly();
        return request.clone({
            setHeaders: {
                Authorization: `Bearer ${accessToken}`
            }
        });
    }

    /**
     * @todo move in common service or auth service once tested
     * logout and redirect to login
     */
    private logout() {
        this.authenticationService.removeSavedUserDetailsAndLogout();
    }
Saurabh Deshmukh
quelle
0

Ich musste folgende Anforderungen lösen:

  • ✅ Token für mehrere Anforderungen nur einmal aktualisieren
  • ✅ Melden Sie den Benutzer ab, wenn refreshToken fehlgeschlagen ist
  • ✅ Melden Sie sich ab, wenn der Benutzer nach der ersten Aktualisierung einen Fehler erhält
  • ✅ Alle Anforderungen in die Warteschlange stellen, während das Token aktualisiert wird

Infolgedessen habe ich verschiedene Optionen gesammelt, um das Token in Angular zu aktualisieren:

  • Brute-Force-Lösung mit tokenRefreshed$BehaviorSubject als Semaphor
  • Verwenden des caughtParameters im catchErrorRxJS-Operator zum erneuten Versuch einer fehlgeschlagenen Anforderung
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    let retries = 0;
    return this.authService.token$.pipe(
      map(token => req.clone({ setHeaders: { Authorization: `Bearer ${token}` } })),
      concatMap(authReq => next.handle(authReq)),
      // Catch the 401 and handle it by refreshing the token and restarting the chain
      // (where a new subscription to this.auth.token will get the latest token).
      catchError((err, restart) => {
        // If the request is unauthorized, try refreshing the token before restarting.
        if (err.status === 401 && retries === 0) {
          retries++;
    
          return concat(this.authService.refreshToken$, restart);
        }
    
        if (retries > 0) {
          this.authService.logout();
        }
    
        return throwError(err);
      })
    );
}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    return this.authService.token$.pipe(
      map(token => req.clone({ setHeaders: { Authorization: `Bearer ${token}` } })),
      concatMap(authReq => next.handle(authReq)),
      retryWhen((errors: Observable<any>) => errors.pipe(
        mergeMap((error, index) => {
          // any other error than 401 with {error: 'invalid_grant'} should be ignored by this retryWhen
          if (error.status !== 401) {
            return throwError(error);
          }
    
          if (index === 0) {
            // first time execute refresh token logic...
            return this.authService.refreshToken$;
          }
    
          this.authService.logout();
          return throwError(error);
        }),
        take(2)
        // first request should refresh token and retry,
        // if there's still an error the second time is the last time and should navigate to login
      )),
    );
}

Alle diese Optionen wurden gründlich getestet und finden Sie im Angular-Refresh-Token-Github-Repo

Yurzui
quelle
-3

Ich habe eine neue Anfrage basierend auf der URL der fehlgeschlagenen Anfrage erstellt und den gleichen Text der fehlgeschlagenen Anfrage gesendet.

 retryFailedRequests() {

this.auth.cachedRequests.forEach(request => {

  // get failed request body
  var payload = (request as any).payload;

  if (request.method == "POST") {
    this.service.post(request.url, payload).subscribe(
      then => {
        // request ok
      },
      error => {
        // error
      });

  }
  else if (request.method == "PUT") {

    this.service.put(request.url, payload).subscribe(
      then => {
       // request ok
      },
      error => {
        // error
      });
  }

  else if (request.method == "DELETE")

    this.service.delete(request.url, payload).subscribe(
      then => {
        // request ok
      },
      error => {
        // error
      });
});

this.auth.clearFailedRequests();        

}}

Johseffer Chepli
quelle
-4

In Ihrer authentication.service.ts sollte ein HttpClient als Abhängigkeit eingefügt werden

constructor(private http: HttpClient) { }

Sie können die Anforderung dann erneut (innerhalb von retryFailedRequests) wie folgt erneut senden:

this.http.request(request).subscribe((response) => {
    // You need to subscribe to observer in order to "retry" your request
});
Attrox_
quelle
Dies war mein erster Gedanke, aber http.request kehrt zurück HttpEvent.
Antoniossss