Solución:
Tienes que distinguir entre todas las solicitudes. Por ejemplo, no desea interceptar su solicitud de inicio de sesión y tampoco la solicitud de token de actualización. SwitchMap es su mejor amigo porque necesita cancelar algunas llamadas para esperar a que su token se actualice.
Entonces, lo que debe hacer es verificar primero las respuestas de error con el estado 401 (no autorizado):
return next.handle(this.addToken(req, this.userService.getAccessToken()))
.pipe(catchError(err => {
if (err instanceof HttpErrorResponse) {
// token is expired refresh and try again
if (err.status === 401) {
return this.handleUnauthorized(req, next);
}
// default error handler
return this.handleError(err);
} else {
return observableThrowError(err);
}
}));
En su función handleUnauthorized, debe actualizar su token y también omitir todas las solicitudes adicionales mientras tanto:
handleUnauthorized (req: HttpRequest<any>, next: HttpHandler): Observable<any> {
if (!this.isRefreshingToken) {
this.isRefreshingToken = true;
// Reset here so that the following requests wait until the token
// comes back from the refreshToken call.
this.tokenSubject.next(null);
// get a new token via userService.refreshToken
return this.userService.refreshToken()
.pipe(switchMap((newToken: string) => {
// did we get a new token retry previous request
if (newToken) {
this.tokenSubject.next(newToken);
return next.handle(this.addToken(req, newToken));
}
// If we don't get a new token, we are in trouble so logout.
this.userService.doLogout();
return observableThrowError('');
})
, catchError(error => {
// If there is an exception calling 'refreshToken', bad news so logout.
this.userService.doLogout();
return observableThrowError('');
})
, finalize(() => {
this.isRefreshingToken = false;
})
);
} else {
return this.tokenSubject
.pipe(
filter(token => token != null)
, take(1)
, switchMap(token => {
return next.handle(this.addToken(req, token));
})
);
}
}
Tenemos un atributo en la clase de interceptor que verifica si ya hay una solicitud de token de actualización en ejecución: this.isRefreshingToken = true;
porque no desea tener varias solicitudes de actualización cuando activa varias solicitudes no autorizadas.
Así que todo dentro del if (!this.isRefreshingToken)
parte se trata de actualizar su token e intentar la solicitud anterior nuevamente.
Todo lo que se maneja en else
es para todas las solicitudes, mientras tanto, mientras su userService actualiza el token, se devuelve un tokenSubject y cuando el token está listo con this.tokenSubject.next(newToken);
Se reintentará cada solicitud omitida.
Aquí este artículo fue la inspiración de origen para el interceptor: https://www.intertech.com/angular-4-tutorial-handling-refresh-token-with-new-httpinterceptor/
EDITAR:
TokenSubject es en realidad un sujeto de comportamiento: tokenSubject: BehaviorSubject<string> = new BehaviorSubject<string>(null);
, lo que significa que cualquier suscriptor nuevo obtendrá el valor actual en la transmisión, que sería el token anterior de la última vez que llamamos this.tokenSubject.next(newToken)
.
Connext(null)
cada nuevo suscriptor no activa el switchMap
parte, por eso filter(token => token != null)
es necesario.
Después this.tokenSubject.next(newToken)
se llama de nuevo con un nuevo token, cada suscriptor activa el switchMap
parte con la ficha nueva. Espero que sea más claro ahora
EDITAR 21.09.2020
Corregir enlace