I am trying to intercept a 401 response, send a refresh token request before trying request again (but with a different header). I have it working except retryWhen
does not give me to modify the original request header. So I've been trying to use catchError
instead but I cannot seem to execute the request again.
Here is my current retryWhen
:
import {Injectable} from '@angular/core';
import {
HttpInterceptor, HttpHandler, HttpRequest, HttpEvent, HttpErrorResponse
}
from '@angular/common/http';
import {Observable} from 'rxjs/Observable';
import {CustomerService} from "../customer/customer.service";
import * as ApplicationSettings from "application-settings";
import {retryWhen, map} from "rxjs/operators";
import {LoginResponse} from "./LoginResponse";
/**
* This takes a request that requires an access_token and refreshes it on 401 errors.
*/
@Injectable()
export class RefreshTokenInterceptor implements HttpInterceptor {
public constructor(private customer: CustomerService) {}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return next.handle(req).pipe(
retryWhen(errors => {
return Observable.create(observer => {
errors.forEach((error: HttpErrorResponse) => {
if (error.status === 401) {
let refresh_token = ApplicationSettings.getString('refresh_token');
return this.customer.refreshToken(refresh_token).subscribe(
(response: LoginResponse) => {
this.customer.setToken(response);
let headers = req.headers.set('Authorization', `Bearer ${response.access_token}`);
console.log(`Bearer ${response.access_token}`);
let newReq = req.clone({headers: headers});
observer.next(next.handle(newReq));
observer.complete();
},
error2 => {
observer.error();
}
);
} else {
observer.error();
}
});
});
})
);
}
}
If I swap out retryWhen
with catchError
:
catchError((err, caught) => {
console.log('err: ' + JSON.stringify(err));
if (err.status === 401) {
console.log('401 !!!! REFRESH MEH!');
let newReqOb: Observable<HttpEvent<any>> = Observable.create(observer => {
console.log('going to refresh token');
let refresh_token = ApplicationSettings.getString('refresh_token');
let refresh = this.customer.refreshToken(refresh_token);
refresh.subscribe((response: LoginResponse) => {
console.log('token refreshed!');
this.customer.setToken(response);
let access_token = ApplicationSettings.getString('access_token');
let headers = req.headers.set('Authorization', `Bearer ${access_token}`);
console.log(`Bearer ${access_token}`);
let newReq = req.clone({headers: headers});
observer.next(next.handle(newReq)); // <-- HERE IT WONT FIRE
observer.complete();
});
});
return newReqOb;
}
return caught;
})
The important part is I am returning next.handle(newReq)
and it doesn't seem to fire the request. If I switch it to next.handle(newReq).subscribe()
, the request will fire but no callbacks are triggered.
Here is the full example with retryWhen
:
import {Inject, Injectable} from '@angular/core';
import {
HttpInterceptor, HttpHandler, HttpRequest, HttpEvent, HttpErrorResponse, HttpClient
}
from '@angular/common/http';
import {Observable, ObservableInput} from 'rxjs/Observable';
import {CustomerService} from "../customer/customer.service";
import * as ApplicationSettings from "application-settings";
import {retryWhen, map, catchError} from "rxjs/operators";
import {LoginResponse} from "./LoginResponse";
import {APP_CONFIG, AppConfig} from "../../app.config";
/**
* This takes a request that requires an access_token and refreshes it on 401 errors.
*/
@Injectable()
export class RefreshTokenInterceptor implements HttpInterceptor {
public constructor(private customer: CustomerService, private http: HttpClient, @Inject(APP_CONFIG) private config: AppConfig) {}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return next.handle(req).pipe(
catchError((err, caught) => {
console.log('err: ' + JSON.stringify(err));
if (err.status === 401) {
console.log('401 !!!! REFRESH MEH!');
let newReqOb: Observable<HttpEvent<any>> = Observable.create(observer => {
console.log('going to refresh token');
let refresh_token = ApplicationSettings.getString('refresh_token');
let refresh = this.customer.refreshToken(refresh_token);
refresh.subscribe((response: LoginResponse) => {
console.log('token refreshed!');
this.customer.setToken(response);
let access_token = ApplicationSettings.getString('access_token');
let headers = req.headers.set('Authorization', `Bearer ${access_token}`);
console.log(`Bearer ${access_token}`);
let newReq = req.clone({headers: headers});
observer.next(next.handle(newReq));
observer.complete();
});
});
return newReqOb;
}
return caught;
})
);
}
}
I did find out the issue, here is the resulting code:
import {Inject, Injectable} from '@angular/core';
import {
HttpInterceptor, HttpHandler, HttpRequest, HttpEvent, HttpErrorResponse, HttpClient
}
from '@angular/common/http';
import {Observable, ObservableInput} from 'rxjs/Observable';
import {CustomerService} from "../customer/customer.service";
import * as ApplicationSettings from "application-settings";
import {catchError, switchMap, finalize} from "rxjs/operators";
import {LoginResponse} from "./LoginResponse";
import {APP_CONFIG, AppConfig} from "../../app.config";
import {RouterExtensions} from "nativescript-angular/router";
/**
* This takes a request that requires an access_token and refreshes it on 401 errors.
* @TODO What happens on 400 errors?
*/
@Injectable()
export class RefreshTokenInterceptor implements HttpInterceptor {
isRefreshingToken: boolean = false;
public constructor(private customer: CustomerService,
private http: HttpClient,
private router: RouterExtensions,
@Inject(APP_CONFIG) private config: AppConfig) {}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
let validate = {
is_api_v1: req.url.indexOf('api/v1') > -1,
is_not_register_end_point: !(req.url.indexOf('api/v1/customers') > -1 && req.method === 'POST')
};
if (validate.is_api_v1 && validate.is_not_register_end_point) {
return next.handle(req).pipe(
catchError((err, caught) => {
if (err instanceof HttpErrorResponse && err.status === 401) {
console.log(req.url);
console.log('Injecting Refresh Token');
return this.handle401Error(req, next);
}
// return caught;
})
);
} else {
return next.handle(req);
}
}
handle401Error(req: HttpRequest<any>, next: HttpHandler) {
//-- Test if we are refreshing so we are not stuck in an infinite loop
if (!this.isRefreshingToken) {
this.isRefreshingToken = true;
let refresh_token = ApplicationSettings.getString('refresh_token');
let refresh = this.customer.refreshToken(refresh_token);
return refresh.pipe(
switchMap((tokenResp: LoginResponse) => {
this.customer.setToken(tokenResp);
let access_token = ApplicationSettings.getString('access_token');
let headers = req.headers.set('Authorization', `Bearer ${access_token}`);
return next.handle(req.clone({headers: headers}));
}),
catchError(error => {
ApplicationSettings.setBoolean("authenticated", false);
this.router.navigate(["/login"], { clearHistory: true, queryParams: {
error: 'Your session is no longer valid, please log in again',
}});
return Observable.throw("");
}),
finalize(() => {
this.isRefreshingToken = false;
})
)
}
}
}