Reputation: 127
I'm making JWT interceptor to handle 401 error. My idea is collect all original requests after 401 to array, then refresh my token, then add a new header to to my request. Here is the code:
type CallerRequest = {
subscriber: Subscriber<any>;
failedRequest: HttpRequest<any>;
};
@Injectable()
export class RefreshTokenInterceptor implements HttpInterceptor {
private refreshInProgress: boolean;
private requests: CallerRequest[] = [];
constructor(
public authService: AuthService,
public customersService: CustomersService,
private http: HttpClient
) {
this.refreshInProgress = false;
}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
if (!req.url.includes('api/')) {
return next.handle(req);
}
const observable = new Observable<HttpEvent<any>>((subscriber) => {
const originalRequestSubscription = next.handle(req)
.subscribe((response) => {
subscriber.next(response);
},
(err) => {
if (err.status === 401) {
this.handleUnauthorizedError(subscriber, req);
} else {
subscriber.error(err);
}
},
() => {
subscriber.complete();
});
return () => {
originalRequestSubscription.unsubscribe();
};
});
return observable;
}
private addToken(request: HttpRequest<any>, token: string) {
return request.clone({setHeaders: {'Authorization': `Bearer ${token}`}});
}
private handleUnauthorizedError(subscriber: Subscriber<any>, request: HttpRequest<any>) {
this.requests.push({ subscriber, failedRequest: request });
if (!this.refreshInProgress) {
this.refreshInProgress = true;
console.log('go refresh');
this.authService.refreshToken()
.pipe(
finalize(() => {
this.refreshInProgress = false;
})
)
.subscribe(
result => {
console.log(result); // <-- I cannot get here
this.repeatFailedRequests(this.authService.getAccessToken());
},
err => {
console.log(err); // <-- And cannot get here
}
)
}
}
private repeatFailedRequests(authHeader: string) {
this.requests.forEach((c) => {
const requestWithNewToken = this.addToken(c.failedRequest, authHeader);
this.repeatRequest(requestWithNewToken, c.subscriber);
});
this.requests = [];
}
private repeatRequest(requestWithNewToken: HttpRequest<any>, subscriber: Subscriber<any>) {
this.http.request(requestWithNewToken).subscribe((res) => {
subscriber.next(res);
},
(err) => {
if (err.status === 401) {
this.authService.removeTokens();
}
subscriber.error(err);
},
() => {
subscriber.complete();
});
}
}
Look at my handleUnauthorizedError method. I can't get results there.
By the way here is my refreshToken:
refreshToken() {
return this.post('api/v0/jwt/refresh/', {refresh: this.getRefreshToken()})
.pipe(tap((tokens: Tokens) => {
this.storeTokens(tokens);
}));
}
What am I doing wrong and how would I solve this issue in a proper way? Thank you!
ADDED:
If I try JWT interceptor from this question Angular 4 Interceptor retry requests after token refresh (most popular answer) I get the same problem - my results from RefreshToken() are not handled.
Upvotes: 3
Views: 1076
Reputation: 127
The problem was in proper handling JWT refresh requests while refreshInProgress. Here is the code which works just perfect:
type CallerRequest = {
subscriber: Subscriber<any>;
failedRequest: HttpRequest<any>;
};
@Injectable()
export class AuthTokenInterceptor implements HttpInterceptor {
private refreshInProgress: boolean;
private requests: CallerRequest[] = [];
private refreshJWTReuest: Subscription;
private straightRequests: string[];
constructor(
public authService: AuthService,
public customersService: CustomersService,
private http: HttpClient
) {
this.refreshInProgress = false;
this.straightRequests = ['currency/'];
}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
if (this.straightRequests.some(v => req.url.includes(v))) {
return next.handle(req);
}
if (req.url.includes('api/v0/jwt/create/')) {
this.refreshInProgress = false;
}
const observable = new Observable<HttpEvent<any>>((subscriber) => {
const originalRequestSubscription = next.handle(this.addToken(req, this.authService.getAccessToken()))
.subscribe((response) => {
subscriber.next(response);
},
(err) => {
if (err.status === 401) {
if (req.url.includes('api/v0/jwt/refresh/')) {
this.refreshJWTReuest.unsubscribe();
this.requests = [];
this.authService.logout();
}
this.handleUnauthorizedError(subscriber, req);
} else {
// notification!!!
subscriber.error(err);
}
},
() => {
subscriber.complete();
});
return () => {
originalRequestSubscription.unsubscribe();
};
});
return observable;
}
private addToken(request: HttpRequest<any>, token: string) {
return request.clone({setHeaders: {'Authorization': `Bearer ${token}`}});
}
private handleUnauthorizedError(subscriber: Subscriber<any>, request: HttpRequest<any>) {
if (!this.refreshInProgress) {
this.refreshInProgress = true;
this.refreshJWTReuest = this.authService.refreshToken()
.subscribe(
result => {
this.refreshInProgress = false;
this.repeatFailedRequests(result.access);
},
err => {
this.authService.logout();
console.log(err);
}
);
}
if (!request.url.includes('api/v0/jwt/refresh/')) {
// avoid refresh requests while refreshInProgress
// this solves a small bug after logout()
this.requests.push({ subscriber, failedRequest: request });
}
}
private repeatFailedRequests(authHeader: string) {
this.requests.forEach((c) => {
const requestWithNewToken = this.addToken(c.failedRequest, authHeader);
this.repeatRequest(requestWithNewToken, c.subscriber);
});
this.refreshJWTReuest.unsubscribe();
this.requests = [];
}
private repeatRequest(requestWithNewToken: HttpRequest<any>, subscriber: Subscriber<any>) {
this.http.request(requestWithNewToken).subscribe((res) => {
subscriber.next(res);
},
(err) => {
if (err.status === 401) {
this.authService.logout();
}
subscriber.error(err);
},
() => {
subscriber.complete();
});
}
}
Upvotes: 1
Reputation: 1523
I think the problem is with your intercept method as you are wrapping the originalObservable it doesn't get called properly.
Try something like this:
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
if (!req.url.includes('api/')) {
return next.handle(req);
}
return next
.handle(req)
.catch((err) => {
if (err.status === 401) {
this.handleUnauthorizedError(req);
} else {
return next.handle(req);
}
})
);
}
Upvotes: 0