I have built an error interceptor in my first Angular application which is all new for me. The interceptor tries to refresh a Firebase authorization token when a 401
response code occures. Therefore I have written the following code:
@Injectable()
export class ErrorInterceptor implements HttpInterceptor {
constructor(private authService: AuthService, private alertService: AlertService) { }
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return next.handle(request).pipe(
catchError(err => {
if (err.status === 401) {
let user = localStorage.getItem('currentUser');
if (!user) {
this.logout(false);
return throwError(err.error);
}
let currentUser = JSON.parse(user);
if (!currentUser || !currentUser.stsTokenManager || !currentUser.stsTokenManager.accessToken) {
this.logout(false);
return throwError(err.error);
}
const reference = this;
this.authService.getToken(currentUser, true).then(t => {
// How do I await and return this properly?
return reference.updateTokenAndRetry(request, next, currentUser, t);
}); // Get token and refresh
}
this.alertService.showAlert({
text: 'Fout tijdens het verzenden van het verzoek',
});
return throwError(err.error);
})
);
}
updateTokenAndRetry(request: HttpRequest<any>, next: HttpHandler, currentUser: any, token: string): Observable<HttpEvent<any>> {
// Update local stored user
currentUser.stsTokenManager.accessToken = token;
localStorage.setItem('currentUser', JSON.stringify(currentUser));
// Add the new token to the request
request = request.clone({
setHeaders: {
Authorization: token,
},
});
return next.handle(request);
}
The token gets refreshed fine. However the network call is not being executed after the refresh, which reference.updateTokenAndRetry(request, next, currentUser, t);
should do.
I assume the reason for this, is that this.authService.getToken(currentUser, true)
returns a Promise
(this is the Firebase plugin and can't be changed). I want to return return reference.updateTokenAndRetry(request, next, currentUser, t);
but this is not possible since it's in an async function block.
How can I await or return the next network call? I can't make the intercept
function async
. I am pretty stuck at this point.
Instead of trying to return an async promise you should convert your promise to an observable using the RxJS 'from' operator as described in this post: Convert promise to observable.
This will result in a correct return type of Observable> for your interceptor.
Your code would look like something like the following (assuming you only send one request at a time):
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return next.handle(request).pipe(
catchError(err => {
if (err.status === 401) {
let user = localStorage.getItem('currentUser');
if (!user) {
this.logout(false);
return throwError(err.error);
}
let currentUser = JSON.parse(user);
if (!currentUser || !currentUser.stsTokenManager || !currentUser.stsTokenManager.accessToken) {
this.logout(false);
return throwError(err.error);
}
// Return a newly created function here
return this.refreshToken(currentUser, request, next);
}
this.alertService.showAlert({
text: 'Fout tijdens het verzenden van het verzoek',
});
return throwError(err.error);
})
);
}
refreshToken(currentUser: any, request: any, next: any) {
// By making use of the from operator of RxJS convert the promise to an observable
return from(this.authService.getToken(currentUser, true)).pipe(
switchMap(t => this.updateTokenAndRetry(request, next, currentUser, t))
)
}
updateTokenAndRetry(request: HttpRequest<any>, next: HttpHandler, currentUser: any, token: string): Observable<HttpEvent<any>> {
// Update local stored user
currentUser.stsTokenManager.accessToken = token;
localStorage.setItem('currentUser', JSON.stringify(currentUser));
// Add the new token to the request
request = request.clone({
setHeaders: {
Authorization: token,
},
});
return next.handle(request);
}
Hope this helps!
Arwin solution works well, but only in an environment where one request is send at the time.
In order to get this to work save the refreshToken
method into an Observable
with the pipe share
. This will allow multiple subscribers but a single result.
Wrap the next.handle(request)
method in another Subject<any>
and return the subject.
If the request fires an error call that isn't a 401
error call subject.error
.
After refreshing the token call this.updateTokenAndRetry(request, next, currentUser, token).subscribe(result => subject.next(result);
to make sure that the request is returned to the initial subscriber.
Below code is pseudo code and should work in your case.
refreshTokenObservable: Observable<any>;
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
let subject = new Subject<any>();
next.handle(request).pipe(
catchError(err => {
if (err.status === 401) {
let user = localStorage.getItem('currentUser');
if (!user) {
this.logout(false);
subject.error(err.error);
return;
}
let currentUser = JSON.parse(user);
if (!currentUser || !currentUser.stsTokenManager || !currentUser.stsTokenManager.accessToken) {
this.logout(false);
subject.error(err.error);
return;
}
// Return a newly created function here
this.refreshToken(currentUser).subscribe(token => {
this.updateTokenAndRetry(request, next, currentUser, token).subscribe(result => subject.next(result);
this.refreshTokenObservable = null; // clear observable for next failed login attempt
});
}
this.alertService.showAlert({
text: 'Fout tijdens het verzenden van het verzoek',
});
subject.error(err.error);
})
).subscribe(result => subject.next(result));
return subject.asObservable();
}
refreshToken(currentUser: any) {
if(this.refreshTokenObservable == null)
{
// By making use of the from operator of RxJS convert the promise to an observable
this.refreshTokenObservable = from(this.authService.getToken(currentUser, true)).pipe(share());
}
return this.refreshTokenObservable;
}
updateTokenAndRetry(request: HttpRequest<any>, next: HttpHandler, currentUser: any, token: string): Observable<HttpEvent<any>> {
// Update local stored user
currentUser.stsTokenManager.accessToken = token;
localStorage.setItem('currentUser', JSON.stringify(currentUser));
// Add the new token to the request
request = request.clone({
setHeaders: {
Authorization: token,
},
});
return next.handle(request);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With