Angular 8 Intercept call to refresh token

本秂侑毒 提交于 2021-01-18 10:17:23

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!