最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Refresh token (JWT) in interceptor Angular 6 - Stack Overflow

programmeradmin0浏览0评论

Initially, I had a function that simply checked for the presence of a token and, if it was not present, sent the user to the login header. Now I need to implement the logic of refreshing a token when it expires with the help of a refreshing token. But I get an error 401. The refresh function does not have time to work and the work in the interceptor goes further to the error. How can I fix the code so that I can wait for the refresh to finish, get a new token and not redirect to the login page?

TokenInterceptor

import {HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest} from "@angular/mon/http";
import {Injectable, Injector} from "@angular/core";
import {AuthService} from "../services/auth.service";
import {Observable, throwError} from "rxjs";
import {catchError, tap} from "rxjs/operators";
import {Router} from "@angular/router";
import {JwtHelperService} from "@auth0/angular-jwt";

@Injectable({
  providedIn: 'root'
})
export class TokenInterceptor implements HttpInterceptor{

  private auth: AuthService;

  constructor(private injector: Injector, private router: Router) {}

  jwtHelper: JwtHelperService = new JwtHelperService();

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {

    this.auth = this.injector.get(AuthService);

    const accToken = this.auth.getToken();
    const refToken = this.auth.getRefreshToken();

    if ( accToken && refToken ) {

      if ( this.jwtHelper.isTokenExpired(accToken) ) {
        this.auth.refreshTokens().pipe(
          tap(
            () => {
              req = req.clone({
                setHeaders: {
                  Authorization: `Bearer ${accToken}`
                }
              });
            }
          )
        )
      } else {
        req = req.clone({
          setHeaders: {
            Authorization: `Bearer ${accToken}`
          }
        });
      }

    }
    return next.handle(req).pipe(
      catchError(
        (error: HttpErrorResponse) => this.handleAuthError(error)
      )
    );
  }

  private handleAuthError(error: HttpErrorResponse): Observable<any>{
    if (error.status === 401) {
      this.router.navigate(['/login'], {
        queryParams: {
          sessionFailed: true
        }
      });
    }
    return throwError(error);
  }

}

AuthService

import {Injectable} from "@angular/core";
import {HttpClient, HttpHeaders} from "@angular/mon/http";
import {Observable, of} from "rxjs";
import {RefreshTokens, Tokens, User} from "../interfaces";
import {map, tap} from "rxjs/operators";

@Injectable({
  providedIn: 'root'
})
export class AuthService{

  private authToken = null;
  private refreshToken = null;

  constructor(private http: HttpClient) {}

  setToken(authToken: string) {
    this.authToken = authToken;
  }

  setRefreshToken(refreshToken: string) {
    this.refreshToken = refreshToken;
  }

  getToken(): string {
    this.authToken = localStorage.getItem('auth-token');
    return this.authToken;
  };

  getRefreshToken(): string {
    this.refreshToken = localStorage.getItem('refresh-token');
    return this.refreshToken;
  };

  isAuthenticated(): boolean {
    return !!this.authToken;
  }

  isRefreshToken(): boolean {
    return !!this.refreshToken;
  }

  refreshTokens(): Observable<any> {

    const httpOptions = {
      headers: new HttpHeaders({
        'Authorization': 'Bearer ' + this.getRefreshToken()
      })
    };

    return this.http.post<RefreshTokens>('/api2/auth/refresh', {}, httpOptions)
      .pipe(
        tap((tokens: RefreshTokens) => {
          localStorage.setItem('auth-token', tokens.access_token);
          localStorage.setItem('refresh-token', tokens.refresh_token);
          this.setToken(tokens.access_token);
          this.setRefreshToken(tokens.refresh_token);
          console.log('Refresh token ok');
        })
      );
  }

}

Initially, I had a function that simply checked for the presence of a token and, if it was not present, sent the user to the login header. Now I need to implement the logic of refreshing a token when it expires with the help of a refreshing token. But I get an error 401. The refresh function does not have time to work and the work in the interceptor goes further to the error. How can I fix the code so that I can wait for the refresh to finish, get a new token and not redirect to the login page?

TokenInterceptor

import {HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest} from "@angular/mon/http";
import {Injectable, Injector} from "@angular/core";
import {AuthService} from "../services/auth.service";
import {Observable, throwError} from "rxjs";
import {catchError, tap} from "rxjs/operators";
import {Router} from "@angular/router";
import {JwtHelperService} from "@auth0/angular-jwt";

@Injectable({
  providedIn: 'root'
})
export class TokenInterceptor implements HttpInterceptor{

  private auth: AuthService;

  constructor(private injector: Injector, private router: Router) {}

  jwtHelper: JwtHelperService = new JwtHelperService();

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {

    this.auth = this.injector.get(AuthService);

    const accToken = this.auth.getToken();
    const refToken = this.auth.getRefreshToken();

    if ( accToken && refToken ) {

      if ( this.jwtHelper.isTokenExpired(accToken) ) {
        this.auth.refreshTokens().pipe(
          tap(
            () => {
              req = req.clone({
                setHeaders: {
                  Authorization: `Bearer ${accToken}`
                }
              });
            }
          )
        )
      } else {
        req = req.clone({
          setHeaders: {
            Authorization: `Bearer ${accToken}`
          }
        });
      }

    }
    return next.handle(req).pipe(
      catchError(
        (error: HttpErrorResponse) => this.handleAuthError(error)
      )
    );
  }

  private handleAuthError(error: HttpErrorResponse): Observable<any>{
    if (error.status === 401) {
      this.router.navigate(['/login'], {
        queryParams: {
          sessionFailed: true
        }
      });
    }
    return throwError(error);
  }

}

AuthService

import {Injectable} from "@angular/core";
import {HttpClient, HttpHeaders} from "@angular/mon/http";
import {Observable, of} from "rxjs";
import {RefreshTokens, Tokens, User} from "../interfaces";
import {map, tap} from "rxjs/operators";

@Injectable({
  providedIn: 'root'
})
export class AuthService{

  private authToken = null;
  private refreshToken = null;

  constructor(private http: HttpClient) {}

  setToken(authToken: string) {
    this.authToken = authToken;
  }

  setRefreshToken(refreshToken: string) {
    this.refreshToken = refreshToken;
  }

  getToken(): string {
    this.authToken = localStorage.getItem('auth-token');
    return this.authToken;
  };

  getRefreshToken(): string {
    this.refreshToken = localStorage.getItem('refresh-token');
    return this.refreshToken;
  };

  isAuthenticated(): boolean {
    return !!this.authToken;
  }

  isRefreshToken(): boolean {
    return !!this.refreshToken;
  }

  refreshTokens(): Observable<any> {

    const httpOptions = {
      headers: new HttpHeaders({
        'Authorization': 'Bearer ' + this.getRefreshToken()
      })
    };

    return this.http.post<RefreshTokens>('/api2/auth/refresh', {}, httpOptions)
      .pipe(
        tap((tokens: RefreshTokens) => {
          localStorage.setItem('auth-token', tokens.access_token);
          localStorage.setItem('refresh-token', tokens.refresh_token);
          this.setToken(tokens.access_token);
          this.setRefreshToken(tokens.refresh_token);
          console.log('Refresh token ok');
        })
      );
  }

}
Share Improve this question edited Oct 29, 2018 at 13:38 Artemy Khodorev asked Oct 24, 2018 at 9:27 Artemy KhodorevArtemy Khodorev 1051 gold badge3 silver badges8 bronze badges 3
  • if the token is already expired your backend send 401 to you. you need to refresh before the token expires – firegloves Commented Oct 24, 2018 at 10:07
  • or you can extend token's expiration date for each received request – firegloves Commented Oct 24, 2018 at 10:10
  • is your problem get fixed can you help with answer how you did it – harkesh kumar Commented Jan 27, 2020 at 6:48
Add a ment  | 

3 Answers 3

Reset to default 1

You have to do something like that:

const firstReq = cloneAndAddHeaders(req);

return next.handle(firstReq).pipe(
   catchError(
      err => {
         if (err instanceof HttpErrorResponse) {
            if (err.status === 401 || err.status === 403) {
               if (firstReq.url === '/api2/auth/refresh') {
                  auth.setToken('');
                  auth.setRefreshToken('');
                  this.router.navigate(['/login']);
               } else {
                  return this.auth.refreshTokens()
                    .pipe(mergeMap(() => next.handle(cloneAndAddHeaders(req))));
               }
            }
            return throwError(err.message || 'Server error');
         }
      }
    )
 );

The implementation of cloneAndAddHeaders should be something like this:

private cloneAndAddHeaders(request: HttpRequest<any>): HttpRequest<any> {
    return request.clone({
       setHeaders: {
           Authorization: `YourToken`
       }
    });
}

In your example you never subscribe to your refreshTokens().pipe() code. Without a subscription, the observable won't execute.

req = this.auth.refreshTokens().pipe(
      switchMap(() => req.clone({
            setHeaders: {
              Authorization: `Bearer ${this.auth.getToken()}`
            }
          }))
      )

This will first call refreshToken and run the tap there, then emit request with the new this.auth.getToken(), note that accToken still have old value as the code is not rerun.

发布评论

评论列表(0)

  1. 暂无评论