Search code examples
javascriptangulartypescriptjwtinterceptor

Angular - Waiting for refresh token function to finish before forwarding the request


I'm working on an auth system in angular with a django backend with jwt. I have an angular interceptor which checks within every request if the access token is still valid and if not, it calls a refreshtoken function and refreshes the access token.

Here is the code of the interceptor:

constructor(private authService:AuthService) {}

  intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
    let access_token = localStorage.getItem("access_token");
    
    if(access_token){
      // Check if access token is no longer valid, if so, refresh it with the refresh token
      if(this.authService.isLoggedOut()){
        
        this.authService.refreshToken();
        access_token = localStorage.getItem("access_token");
      }
      const cloned = request.clone({
        headers: request.headers.set("Authorization", "Bearer " + access_token)
      });
      return next.handle(cloned);
    } 
    else{
      return next.handle(request);
    }

It gets the access_token and checks the validity with the help of the auth service. My problem is that if its unvalid its calls the refreshToken() function which looks like this,

  refreshToken(){
    let refresh_token = localStorage.getItem("refresh_token");
    console.log("BEFORE REFRESH: " + localStorage.getItem("access_token"));
 
    return this.http.post(`${apiUrl}token/refresh/`, {"refresh" : refresh_token}).subscribe(res => {
      let access_token = res["access"]
      const expiresAt = this.tokenExpiresAt(access_token);
      localStorage.setItem("access_token", access_token);
      localStorage.setItem("expires_at", JSON.stringify(expiresAt.valueOf()));

      console.log("AFTER REFRESH: " + localStorage.getItem("access_token"));
    });
  }

but it doesn't wait for the refresh token function to finish and returns the handle. So the first request with an invalid token throws an error and only the next requests are fine.

How can I revise this to wait for refreshToken() to finish?

Thanks for your time!


Solution

  • I combined the answers from multiple questions and finally came up with this solution:

    Interceptor:

    intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
        let access_token = localStorage.getItem("access_token");
      
        if (access_token) {
          // Check if access token is no longer valid, if so, refresh it with the refresh token
          if (this.authService.isLoggedOut()) {
            return from(this.authService.refreshToken()).pipe(
              mergeMap((access_token) => {
                localStorage.setItem("access_token", access_token);
                const cloned = request.clone({
                  headers: request.headers.set("Authorization", "Bearer " + access_token),
                });
                return next.handle(cloned);
              })
            );
          }
          const cloned = request.clone({
            headers: request.headers.set("Authorization", "Bearer " + access_token),
          });
          return next.handle(cloned);
        } else {
          return next.handle(request);
        }
      }
    

    refreshToken:

    async refreshToken(): Promise<string> {
        let refresh_token = localStorage.getItem("refresh_token"); 
        const res$ = this.http
        .post(`${apiUrl}token/refresh/`, { refresh: refresh_token })
        .pipe(map((res) => res["access"]))
        .pipe(first());
        const res = await lastValueFrom(res$);
    
        const expiresAt = this.tokenExpiresAt(res);
        localStorage.setItem("access_token", res);
        localStorage.setItem("expires_at", JSON.stringify(expiresAt.valueOf()));
        console.log("Refreshed Access Token");
        return res;
      }