Reputation: 852
I'm trying to handle 401 response from interceptor using httpClientModule. The authentication is based on JWT, with accessToken and refreshToken. If the accessToken is expired, I need to request the api to get a new one with the refreshToken. I would like to block the request when the a new token should be generated, get a new one, and then make the request with the new accessToken attached.
In this way, what is the best method to make http request from the interceptor ?
My interceptor :
@Injectable()
export class JwtService implements HttpInterceptor {
constructor(public inj: Injector){}
intercept(req : HttpRequest<any>, next : HttpHandler) : Observable<HttpEvent<any>> {
if ( this.shouldGenerateNewAccessToken(req.url) ){
const auth = this.inj.get(AuthService);
auth.getNewAccessToken().then( token => {
if (token){
const headers = { 'Authorization' : token };
const clone = req.clone( { setHeaders : headers } );
return next.handle(clone);
} else { return next.handle(req); }
}, error => {
return next.handle(req);
});
}
else {
if (APP_CONFIG['accessToken']){
const headers = { 'Authorization' : APP_CONFIG['accessToken'] };
const clone = req.clone( { setHeaders : headers });
return next.handle(clone);
} else {
return next.handle(req);
}
}
}
shouldGenerateNewAccessToken(url : string) : Boolean {
let lastupdate = APP_CONFIG['accessTokenTimestamp'];
let now = new Date().getTime();
// Let say the token expires after 5s
if ((now - lastupdate) > 5000 && APP_CONFIG['refreshToken'] && url != APP_CONFIG['apiEndPont']+'getaccesstoken'){
return true;
}
else
return false;
}
Auth logic
getNewAccessToken() : Promise<any>{
return new Promise( (resolve, reject)=>{
this.http.post(this.api+ 'getaccesstoken', JSON.stringify({refreshToken: APP_CONFIG['refreshToken'] }), { "headers" : this.headers } ).subscribe( data => {
let res : any = data;
APP_CONFIG['accessToken'] = res.accessToken;
APP_CONFIG['accessTokenTimestamp'] = new Date().getTime();
resolve(APP_CONFIG['accessToken']);
}, err => {console.log('error'); reject(null); })
});
}
getuserinfos(){
return this.http.get(this.api+ 'getuserinfos', { "headers" : this.headers } ).subscribe( data => {
console.log('result getUserInfos =>', data);
},
( err : HttpErrorResponse ) => {
if ( err.error instanceof Error ) { console.log('An error occurred requete login:', err.error.message); }
else {
console.log('error => ', err)
}
});
}
I get the following error when I call getUserInfos() and the token is expired :
error => TypeError: You provided 'undefined' where a stream was expected. You can provide an Observable, Promise, Array, or Iterable.
Is this linked to this behavior ?
More rarely, an interceptor may choose to completely handle the request itself, and compose a new event stream instead of invoking next.handle(). This is acceptable behavior, but keep in mind further interceptors will be skipped entirely. It is also rare but valid for an interceptor to return multiple responses on the event stream for a single request. Source
Upvotes: 1
Views: 1313
Reputation: 852
I finally managed it with a different implementation. Here is my interceptor :
@Injectable()
export class JwtService implements HttpInterceptor {
constructor(public inj: Injector){}
private fixUrl(url: string) {
if (url.indexOf('http://') >= 0 || url.indexOf('https://') >= 0)
return url;
else
return APP_CONFIG['apiEndPoint'] + url;
}
intercept(req : HttpRequest<any>, next : HttpHandler) : Observable<HttpEvent<any>> {
// Clone request
var headers = {}
if (APP_CONFIG['accessToken']){
headers = { 'Authorization' : APP_CONFIG['accessToken'] };
}
const cloneRequest = req.clone( { setHeaders : headers });
return next.handle(cloneRequest).do(data => {
if (data instanceof HttpResponse) {
// Some logic
}
})
.catch((res)=> {
if (res.status === 401 || res.status === 403) {
if (APP_CONFIG['accessToken'])
{
const auth = this.inj.get(AuthService);
return auth.getUpdatedAccessToken().flatMap( token => {
// Clone the previous request
let clonedRequestRepeat = req.clone({
headers: req.headers.set('Authorization' , APP_CONFIG['accessToken'] ),
url: this.fixUrl(req.url)
});
// Request agin
return next.handle(clonedRequestRepeat).do(event => {
if (event instanceof HttpResponse) {
console.log('Repeat response of server : ', event);
}
});
});
}else { return Observable.throw('Not authenticated'); }
}
else { // Not 401
return Observable.throw(res);
}
})
}
}
AuthService.ts
getUpdatedAccessToken() : Observable<any>{
return this.http.post(this.api+ 'getaccesstoken', JSON.stringify({refreshToken: APP_CONFIG['refreshToken'] }), { "headers" : this.headers } )
.map((response: any) => {
if (response.code == 0){
APP_CONFIG['accessToken'] = response.accessToken;
}
return response
})
}
Upvotes: 2