Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Retry Http request with new headers in Angular 4.3 using Interceptor

How to handle cloned request with new headers?

I am trying to perform a cloned request in the new interceptors with the new token generated after the first request fail!

  • If err 401 -> refresh token
  • send back a copy of the previous request with the new headers

Here what I tried:

  refreshSessionToken() {
    return this.http.post('/connect', {})
      .map(res => {
        const token = res.json();
        localStorage.setItem('token', token);
      return token;
      });
  }


// Get the headers
const headers = getAuthenticationHeaders();
const reqMain = req.clone({headers: headers});
return next.handle(reqMain).catch(err => {

       this.refreshSessionToken()
            .subscribe(token => {
                const t = token.token_id;
                const clonedReq = req.clone({headers: req.headers.set('access-token', t)});

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

getting a log of the clonedReq I can see that the token is refreshed, but the request (clonedReq) inside the subscribe is not performed, why?!

I tried other approaches on how to refresh the JWT but it doesn't seem to work in my case, any help on how to deal with it?

Thanks!

What do I expect to be my result?

  • Send 1# HTTP request
  • 1# request fail
  • Refresh token (get/set token)
  • Clone previous request and add refreshed token
  • Send 2# HTTP request
  • 2# request success

Similiar issues:

http-interceptor-refresh-jwt-token

like image 624
39ro Avatar asked Sep 17 '25 08:09

39ro


2 Answers

Following generalized method could be used to intercept as well as add/remove additional information to call and responses.

All right, Here the complete code.

InterceptedHttp.ts


import { Injectable } from "@angular/core";
import { RequestOptions, Http, Headers, Response, RequestMethod, URLSearchParams } from "@angular/http";
import { Observable, Observer } from 'rxjs/Rx';

import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch';
import "rxjs/add/operator/mergeMap";

@Injectable()
export class InterceptedHttp {

    constructor(private http: Http) { }

    getRequestOption(method: RequestMethod | string, data?: any, params?: any): RequestOptions {
        let options = new RequestOptions();
        options.headers = new Headers();
        //options.headers.append('Content-Type', 'application/json');
        options.method = method;

        let token: string = localStorage.getItem('token');
        //if (token) options.headers.append('Authorization', 'Bearer ' + token);

        if (data) options.body = data;

        if (params) {
            options.search = new URLSearchParams();
            let keys: string[] = Object.keys(params);

            keys.forEach((key, index) => {
                options.search.set(key, params[key]);
            });
        }

        return options;
    }

    refreshSessionToken(): Observable<string> {
        //Put some user identification data
        let userData: any = {id: 'abc'};
        return this.http.post('/refreshToken', userData)
            .map(res => {
                let token = res.json();
                localStorage.setItem('token', token);
                return token;
            });
    }

    getApiResponse<T>(url: string, method: RequestMethod | string, data?: Object): Observable<T> {
        let op1: RequestOptions = this.getRequestOption(method, data);
       return  this.http.request(url, op1)
            .catch((err) => {
                // UnAuthorised, 401
                if (err.status == 401) {
                    return this.refreshSessionToken().flatMap(t => {
                        let op2 = this.getRequestOption(method, data);
                        return this.http.request(url, op2);
                    });
                }
                throw err;
            })
            .map((response: Response) => {
                let ret: T = response.json();
                return ret;
            });
    }

    get<T>(url: string): Observable<T> {
        return this.getApiResponse<T>(url, RequestMethod.Get);
    }

    post<T, R>(url: string, body: T): Observable<R> {
        return this.getApiResponse<R>(url, RequestMethod.Post, body);
    }

    put<T, R>(url: string, body: T): Observable<R> {
        return this.getApiResponse<R>(url, RequestMethod.Put, body);
    }

    delete<T>(url: string): Observable<T> {
        return this.getApiResponse<T>(url, RequestMethod.Delete);
    }
}

DataService.ts

    import { Injectable } from '@angular/core';
    import { Observable } from 'rxjs/Observable';
    import { User } from './User';
    import { InterceptedHttp } from './http.interceptor';

    @Injectable()
    export class DataService {
        constructor(private apiHandler: InterceptedHttp) { }

        getAll(): Observable<User[]> {
            return this.apiHandler.get<User[]>('http://mocker.egen.io/users');
        }
    }

User.ts

    export class User {
        id?: number;

        firstName?: string;

        lastName?: string;
    }

AppComponent.ts

    import { Component } from '@angular/core';
    import { DataService } from './user-data.service';
    import { User } from './User';

    @Component({
      selector: 'app-root',
      templateUrl: './app.component.html',
      styleUrls: ['./app.component.css']
    })
    export class AppComponent {
      title = 'app works!';
      users: User[];

      constructor(dataService: DataService){
        dataService.getAll().subscribe((u) => {
          this.users = u;
        });
      }  
    }

app.component.html

<h1>
  <table>
    <tr *ngFor="let item of users; let i = index">
      <td> {{item.firstName}} </td>
    </tr>
  </table>
</h1>
like image 190
codeSetter Avatar answered Sep 19 '25 14:09

codeSetter


Here is the solution which I got working with the latest version of Angular (7.0.0) and rxjs (6.3.3). Hope it helps.

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) {
      // Hit refresh-token API passing the refresh token stored into the request
      // to get new access token and refresh token pair
      this.sessionService.refreshToken().subscribe(this._refreshSubject);
    }
    return this._refreshSubject;
  }

  private _checkTokenExpiryErr(error: HttpErrorResponse): boolean {
    return (
      error.status &&
      error.status === 401 &&
      error.error &&
      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;
  }
}

I answered a similar question here. You can also have a read at my article here for the understanding of the code.

like image 36
Samarpan Avatar answered Sep 19 '25 13:09

Samarpan