Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
268 views
in Technique[技术] by (71.8m points)

authentication - Angular interceptors : how to manage retry (nested next.handle())?

I've been working to construct an Interceptor that will:

  • Intercept every request
  • Add authentication tokens in local storage
  • Do the request anyway
  • Get back this request, log the result and if there is a 401/403:
    • Call an entrypoint to refresh token
    • At the reception, retry the request
    • Check the result and log if valid or not

In this case, I must manage two types of retry (for token management):

  • One for django
  • One for pryv that is another solution and have differents tokens that must be added in the request

I have trouble managing two nexted next.handle() The second one is never fired and the request is not retried...

Here's my code :

   export class HttpInterceptorService implements HttpInterceptor {
        constructor(private authService: AuthenticationService,
                private pryvAccessesService: PryvAccessesService) { }
    
        intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
            const uuid = new Date().valueOf();
            if (!req.url.includes('api/authentication/token/')) {
                const authReq = this.addTokensRequest(req);
                console.log(uuid + " : sending request with tokens : " + req.url);
                return next.handle(authReq).pipe(
                    map(res => {console.log(uuid + ' : request succedeed ! ' + req.url); return res;}),
                    catchError(err => {
                        console.log(uuid + ' : request failed ! ' + req.url);
                        // Django invalid token
                        if (err.status === 401) {
                            console.log(uuid + " : sending django refresh request : " + req.url);
                            return this.authService.refresh().pipe(
                                map((res: any) => {
                                    const authReqRefreshed = this.addTokensRequest(req);
                                    console.log(uuid + " : sending refreshed request : " + req.url);
                                    return next.handle(authReqRefreshed).pipe(
                                        map(res => {console.log( uuid + ' : success refreshed request ! ' + req.url); return res;}),
                                        catchError(err => {console.log(uuid + " failed refreshed request : " + req.url); return throwError(err);})
                                    );
                                })
                            )
                        // Pryv invalid token
                        } else if(err.status === 400 && err.error['error'] == 'Invalid access token within Pryv usage') {
                            console.log(uuid + ' : bad pryv token : ' + req.url);
                            return this.getNewTokenForUrl(req).pipe(
                                map((res: string) => {
                                    console.log(uuid + " : success get new token for URL : " + req.url);
                                    const authReqNewPryv = this.addTokensRequest(req); // <= code goes up to here
                                    return next.handle(authReqNewPryv).pipe(
                                        map(res => {console.log(uuid + ' : success request with new pryv token ! ' + req.url); return res;}), // <= this is never triggered
                                        catchError((err) => {console.log(uuid + " : error request with new pryv token : " + req.url); return throwError(err);}) // <= this also is never triggered
                                    );
                                }), catchError((err) => {console.log(uuid + " fail get new pryv token : " + req.url); return throwError(err);})
                            );
                        // Unrelated error
                        } else {
                            console.log(uuid + " error not related to authentication : " + req.url); return throwError(err);
                        }
                    })
                );
            } else {
                console.log(uuid + " authentication, passing thru " + req.url); return next.handle(req);
            }

Here for example, the 'console.log(uuid + " : success get new token for URL : " + req.url);' log will never be shown and the retry request will never be sent. How to manage it ? I've tried sending another HttpRequest, but the interceptor is catching it...

Thanks !

question from:https://stackoverflow.com/questions/65849090/angular-interceptors-how-to-manage-retry-nested-next-handle

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

You should split the responsibles of this interceptor to multiple interceptor, something like below:

Inteceptors token in AppModule:

// Important: Order of Interceptors matters!
export const httpInterceptorProviders = [
    { provide: HTTP_INTERCEPTORS, useClass: RetryInterceptor, multi: true },
    { provide: HTTP_INTERCEPTORS, useClass: AuthTokensManagementInterceptor, multi: true },
    { provide: HTTP_INTERCEPTORS, useClass: TelemetryInterceptor, multi: true },
];

Auth token interceptor:

public intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        return of(req).pipe(
            mergeMap(() => getAcessToken() /*and add it to local storage*/)
            mergeMap((accessToken: string) => {
                if (accessToken) {
                    req = req.clone({
                        headers: req.headers.set(AuthorizationHeader, `${accessToken}`)
                    })
                }
                return next.handle(req).pipe(
                    catchError((error: HttpErrorResponse) => {
                        if (error instanceof HttpErrorResponse) {
                            if (error.status === 401)
                                this.authTokensManager.invalidateAccessToken(error.url);
                            }
                        }

                        return throwError(error);
                    })
                );
        }));
    }

Retry Interceptor:

public intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        return of(req).pipe(
            mergeMap((req: HttpRequest<any>) => {
                return next.handle(req).pipe(
                    // Will continue the pipe if a request attempt has succeeded without errors, or exceeded max retries.
                    retryWhen((errors: Observable<any>) => errors.pipe(
                        // Enable only 2 retries, +1 for the original request.
                        take(this.maxRetries + 1),
                        skipWhile(() => disableRetry),
                        mergeMap((err: HttpErrorResponse, i: number) => {
                            if (i >= this.maxRetries) {
                                return throwError(err);
                            }

                            const errorCode = err.status;
                            
                            if (errorCode == 401) {
                                return of(err);
                            }

                            return throwError(err);
                        }),
                    ))
                )
            })
        );
    }

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...