Issue
I have implemented a refresh token process in an http interceptor for an Ionic app using Angular, I think I missed something to get the code working properly:
refreshTokens token function:
async refreshTokens() {
return new Promise((resolve, reject) => {
this.dataService
.post(this.apiUriService.refreshTokenUri(), {
refresh_token: this.authData.refreshToken,
})
.pipe(take(1))
.subscribe({
next: (res: any) => {
if (!res) {
return reject(null);
}
const resData = res.data ? res.data : null;
this.utils.setItemOnLocalStorage(
Constants.authDataStorageItemName,
resData
);
resolve(resData);
},
error: (error) => {
return reject(error);
},
});
});
}
And handle401Error function:
async handle401Error(req: HttpRequest<any>, next: HttpHandler, error: any) {
return from(this.refreshTokens()).pipe(
switchMap((res: any) => {
console.log('switchMap fired!');
const request = this.addToken(req, res.access_token);
return next.handle(request);
}),
catchError((error) => {
this.logoutUser();
return throwError(error);
})
);
}
Here is the full interceptor class code :
import { Injectable } from '@angular/core';
import { Router } from '@angular/router';
import {
HttpRequest,
HttpResponse,
HttpHandler,
HttpEvent,
HttpInterceptor,
HttpErrorResponse,
} from '@angular/common/http';
import {
Observable,
throwError,
BehaviorSubject,
tap,
take,
from,
switchMap,
catchError,
} from 'rxjs';
import { AuthService } from './auth/auth.service';
import { ApiUriService } from './api-uri.service';
import { DataService } from './data.service';
import { Constants } from '../constants/constants';
import { CommonUtilsService } from './common-utils.service';
import { UserService } from './user/user.service';
@Injectable()
export class CommonHttpInterceptor implements HttpInterceptor {
tokenSubject: BehaviorSubject<string> = new BehaviorSubject<string>('');
get authData() {
return this.authService.readUserInfoFromLocalStorage();
}
constructor(
private authService: AuthService,
private router: Router,
private apiUriService: ApiUriService,
private dataService: DataService,
private userService: UserService,
private utils: CommonUtilsService
) {}
intercept(
request: HttpRequest<unknown>,
next: HttpHandler
): Observable<HttpEvent<unknown>> {
const userInfoData = this.authService.authUserData;
const headers: any = {};
if (userInfoData && userInfoData.accessToken) {
headers['Authorization'] = `Bearer ${userInfoData.accessToken}`;
}
const clonedRequest = request.clone({ setHeaders: headers });
return next.handle(clonedRequest).pipe(
tap(
(event: HttpEvent<any>) => {
if (event instanceof HttpResponse) {
// case when we need to transform the response
}
},
(error: HttpErrorResponse) => {
// handle refresh token in case of the access token is expired
if (error.status === 401) {
return this.handle401Error(request, next);
}
return throwError(error);
}
)
);
}
logoutUser() {
this.authService.doLogout();
this.router.navigate(['auth/login']);
}
handle401Error(req: HttpRequest<any>, next: HttpHandler) {
return from(this.refreshTokens()).pipe(
switchMap((res: any) => {
console.log('switchMap fired!');
const request = this.addToken(req, res.access_token);
return next.handle(request);
}),
catchError((error) => {
this.logoutUser();
return throwError(error);
})
);
}
// refresh access token OR logout user if request failed
refreshTokens() {
return new Promise((resolve, reject) => {
this.dataService
.post(this.apiUriService.refreshTokenUri(), {
refresh_token: this.authData.refreshToken,
})
.pipe(take(1))
.subscribe({
next: (res: any) => {
if (!res) {
return reject(null);
}
const resData = res.data ? res.data : null;
this.utils.setItemOnLocalStorage(
Constants.authDataStorageItemName,
resData
);
resolve(resData);
},
error: (error) => {
return reject(error);
},
});
});
}
//add token to authorization header
private addToken(request: HttpRequest<any>, token: any) {
const newToken = this.authService.shiftoCookieValue
? this.authService.shiftoCookieValue
: token;
if (newToken) {
let clone: HttpRequest<any>;
clone = request.clone({
setHeaders: {
Authorization: `Bearer ${token}`,
},
});
return clone;
}
return request;
}
}
My problem is the switchMap not fired and not logging any log in the console. So no next req sent.
Solution
The problem is that your error handler method is called inside of tap()
and the resulting Observable is being returned. But the return value is ignored in tap()
, so the Observable won't be subscribed.
You can use catchError, and the returned Observable will be subscribed to.
return next.handle(clonedRequest).pipe(
catchError((error: HttpErrorResponse) => {
if (error.status === 401) {
return this.handle401Error(request, next);
}
return throwError(() => error);
})
);
Answered By - Bastian Bräu
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.