Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular 4 Interceptor retry requests after token refresh

Hi I am trying to figure out how implement the new angular interceptors and handle 401 unauthorized errors by refreshing the token and retrying the request. This is the guide I have been following: https://ryanchenkie.com/angular-authentication-using-the-http-client-and-http-interceptors

I am successfully caching the failed requests and can refresh the token but I cannot figure out how to resend the requests that previously failed. I also want to get this to work with the resolvers I am currently using.

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     } ); } 

The above retryFailedRequests() file is what I can't figure out. How do I resend the requests and make them available to the route through the resolver after retrying?

This is all the relevant code if that helps: https://gist.github.com/joshharms/00d8159900897dc5bed45757e30405f9

like image 961
Kovaci Avatar asked Jul 19 '17 22:07

Kovaci


People also ask

How do I retry any failed HTTP request with angular interceptor?

As you probably already know this interceptor can be used to modify any incoming or outgoing HTTP requests. How do we get the benefits of an Angular interceptor and use it to retry any failed HTTP request? The answer is simple. To add the retry capability we'll use the retry function that triggers a retry when the Observable fails.

How to retry a request from an interceptor?

In your interceptor on response refresh the token and return the error. When your service gets back the error but now retry operator is being used so it will retry the request and this time with the refreshed token (Interceptor uses refreshed token to add in the header.) Show activity on this post.

How to refresh token on 401 response in RxJS?

Other than that retry operator may help with your logic of refreshing token on 401 response. Use the RxJS retry operator in your service where you are making a request. It accepts a retryCount argument. If not provided, it will retry the sequence indefinitely. In your interceptor on response refresh the token and return the error.

What is the retrycount argument used for in an interceptor?

It accepts a retryCount argument. If not provided, it will retry the sequence indefinitely. In your interceptor on response refresh the token and return the error.


1 Answers

My final solution. Works with parallel requests.

UPDATE: The code updated with Angular 9 / RxJS 6, error handling and fix looping when refreshToken fails

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 }; 
like image 195
Andrei Ostrovski Avatar answered Oct 19 '22 02:10

Andrei Ostrovski