0

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!

Twenny
  • 45
  • 4
  • You could look into this question: https://stackoverflow.com/questions/45345354/how-use-async-service-into-angular-httpclient-interceptor So you can basically wait for your auth response and continue. – Schrader Jan 02 '23 at 14:13

2 Answers2

1

you can use rxjs operators such as switchMap

    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 this.authService.refreshToken().pipe(
                switchMap(token => {
                    const cloned = request.clone({
                        headers: request.headers.set("Authorization", "Bearer " + access_token)
                    });
                    return next.handle(cloned);
                })
            )
        }
    } else {
        return next.handle(request);
    }
}

and in refreshToken function - make it return Observsble and set the localStorage inside pipe

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}).pipe(tap(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"));
});
 )
 }
Eli Porush
  • 1,339
  • 1
  • 7
  • 18
  • First of all, thanks for your answer! I think I get the idea but sadly it gives me an "ERROR TypeError: You provided 'undefined' where a stream was expected. You can provide an Observable, Promise, ... " error – Twenny Jan 02 '23 at 20:08
  • I tried to debug it and it seems like it crashes inside of the refreshtoken function but I'm not really sure – Twenny Jan 02 '23 at 20:35
  • There's two if conditions. Make sure you return value in all cases, because this function must return value (as the error you get.) – Eli Porush Jan 02 '23 at 22:52
1

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;
  }
Twenny
  • 45
  • 4