问题
I am trying to refresh access token if current access token is expired.
I am sending multiple requests at one time and I want to make a kind of a queue, so other requests won't request refreshing token route.
I've googled some best practises and examples and found out the following solution for Angular 6 and rxjs v6, which is using BehaviourSubject and switchMaps. (please see attached code)
However I am using Angular 8 (8.1) and rxjs v6.4 and this solution does not work for me.
It simply does not reach switchMap
in this.authService.requestAccessToken().pipe
. (Tested using console.log)
However if I comment return this.refreshTokenSubject.pipe
and return next.handle(request)
it reaches that switchMap, but my other requests are failed.
Do you know if anything has been changed or should I try doing this in another way?
- TokenInterceptor
import { Injectable } from '@angular/core';
import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/common/http';
import { AuthService } from './auth.service';
import { Observable, BehaviorSubject, Subject } from 'rxjs';
import { switchMap, take, filter } from 'rxjs/operators';
@Injectable()
export class TokenInterceptor implements HttpInterceptor {
private refreshTokenInProgress = false;
private refreshTokenSubject: Subject<any> = new BehaviorSubject<any>(null);
constructor(public authService: AuthService) { }
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const accessExpired = this.authService.isAccessTokenExpired();
const refreshExpired = this.authService.isRefreshTokenExpired();
if (accessExpired && refreshExpired) {
return next.handle(request);
}
if (accessExpired && !refreshExpired) {
if (!this.refreshTokenInProgress) {
this.refreshTokenInProgress = true;
this.refreshTokenSubject.next(null);
return this.authService.requestAccessToken().pipe(
switchMap((authResponse) => {
this.authService.saveToken(AuthService.TOKEN_NAME, authResponse.accessToken);
this.authService.saveToken(AuthService.REFRESH_TOKEN_NAME, authResponse.refreshToken);
this.refreshTokenInProgress = false;
this.refreshTokenSubject.next(authResponse.refreshToken);
return next.handle(this.injectToken(request));
}),
);
} else {
return this.refreshTokenSubject.pipe(
filter(result => result !== null),
take(1),
switchMap((res) => {
return next.handle(this.injectToken(request))
})
);
}
}
if (!accessExpired) {
return next.handle(this.injectToken(request));
}
}
injectToken(request: HttpRequest<any>) {
const token = this.authService.getToken(AuthService.TOKEN_NAME);
return request.clone({
setHeaders: {
Authorization: `Bearer ${token}`
}
});
}
}
- requestAccessToken
requestAccessToken(): Observable<any> {
const refreshToken = this.getToken(AuthService.REFRESH_TOKEN_NAME);
return this.http.post(`${this.basePath}/auth/refresh`, { refreshToken });
}
UPD 1
So I used these sources to write my interceptor:
https://itnext.io/angular-tutorial-implement-refresh-token-with-httpinterceptor-bfa27b966f57 (Angular 4 solution, I believe it edpends on rxjs version)
https://github.com/melcor76/interceptors/blob/master/src/app/interceptors/auth.interceptor.ts
UPD 2
I've excluded refresh
request from interceptor scope and now it's working
Thanks to @JBNizet
回答1:
I've excluded refresh request from interceptor scope and now it's working. I've made a temporary fix in order to see it's working in the fastest way.
Now my TokenInterceptor looks like:
import { Injectable } from '@angular/core';
import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/common/http';
import { AuthService } from './auth.service';
import { Observable, BehaviorSubject, Subject } from 'rxjs';
import { switchMap, take, filter } from 'rxjs/operators';
@Injectable()
export class TokenInterceptor implements HttpInterceptor {
private refreshTokenInProgress = false;
private refreshTokenSubject: Subject<any> = new BehaviorSubject<any>(null);
constructor(public authService: AuthService) { }
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
if (request.url.indexOf('refresh') !== -1) {
return next.handle(request);
}
const accessExpired = this.authService.isAccessTokenExpired();
const refreshExpired = this.authService.isRefreshTokenExpired();
if (accessExpired && refreshExpired) {
return next.handle(request);
}
if (accessExpired && !refreshExpired) {
if (!this.refreshTokenInProgress) {
this.refreshTokenInProgress = true;
this.refreshTokenSubject.next(null);
return this.authService.requestAccessToken().pipe(
switchMap((authResponse) => {
this.authService.saveToken(AuthService.TOKEN_NAME, authResponse.accessToken);
this.authService.saveToken(AuthService.REFRESH_TOKEN_NAME, authResponse.refreshToken);
this.refreshTokenInProgress = false;
this.refreshTokenSubject.next(authResponse.refreshToken);
return next.handle(this.injectToken(request));
}),
);
} else {
return this.refreshTokenSubject.pipe(
filter(result => result !== null),
take(1),
switchMap((res) => {
return next.handle(this.injectToken(request))
})
);
}
}
if (!accessExpired) {
return next.handle(this.injectToken(request));
}
}
injectToken(request: HttpRequest<any>) {
const token = this.authService.getToken(AuthService.TOKEN_NAME);
return request.clone({
setHeaders: {
Authorization: `Bearer ${token}`
}
});
}
}
Thanks to @JBNizet
回答2:
@AntGrisha , thanks for posting the solution after correcting.Saved me lots of work. I have added error handling as well.Might help some beginner like me and so posting it here.I have used angular 9.1.5 version.
Should pipe in the request handler as below.
return next.handle(this.injectToken(req)).pipe(
catchError(this.handleError)
Error handler function definition
injectToken(request: HttpRequest<any>) {
const token = this.authService.getToken(AuthService.TOKEN_NAME);
return request.clone({
setHeaders: {
Authorization: `Bearer ${token}`
}
});
}
//Error handling function
handleError(error: HttpErrorResponse){
console.log('Error Occurred: '+error);
if(error.status==401){
return throwError("Unauthorized!");
}
else{
return throwError(error);
}
来源:https://stackoverflow.com/questions/57637923/angular-8-intercept-call-to-refresh-token