1
votes

I am using angular and asp.net core I have implemented JWT Token and refresh token with the help of
this artical I have written code to check Jwt is valid in Authguard if jwt is not valid so with refresh token will make call to api and get the new Jwt and refresh token . this scenario is happing only if I am performing any event like refreshing page and navigating to another page. I want to know is there any way that if our jwt token expire without performing any event angular detect it and automatically call to api with refersh token and get the new jwt token

here are codes

authguard

here in authguard isUserAuthenticated() is going to check jwt expired or not if expired it will make api call with refresh token and get the new jwt and refresh token

async canActivate(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot): Promise<boolean> {
    if((await this._authService.isUserAuthenticated()) === true){
      return true;
    }
    else{
      this._router.navigate([RouteConstant.SIGN_IN]);
      return false;
    }
  }

public async isUserAuthenticated(){
    if(this._storageService.getToken() !== null){
      if(!this._jwtHelperService.isTokenExpired(this._storageService.getToken()?.toString())){
        return true;
      }
      else{
        if(!this._storageService.refreshTokenExists()){
          return false;
        }
        else{
          let authTokenClient: AuthTokenClient = {
            token: this._storageService.getToken() as string,
            refreshToken: this._storageService.getRefreshToken() as string
          };
          let response: AuthToken = await this._serverService.refreshAuthToken(authTokenClient).toPromise<AuthToken>();
          console.log(response);
          this.setAuth(response);
          return !this._jwtHelperService.isTokenExpired(this._storageService.getToken()?.toString());
        }
      }
    }
    else{
      return false;
    }
  }

  }

  public setAuth(authToken: AuthToken){
    this._storageService.saveToken(authToken.token?.toString());
    this._storageService.saveRefreshToken(authToken.refreshToken);
  }
  public async refreshAuthToken(authTokenClient: AuthTokenClient) {
    const response = await this._http.post<AuthToken>(environment.apiHost + UrlConstant.ACCOUNT_REFRESH, authTokenClient,{observe: 'response'}).toPromise();
    console.log(response);
    const newToken = (<any>response).body.token;
            console.log(newToken);
            const newRefreshToken = (<any>response).body.refreshToken;
            console.log(newRefreshToken);
            localStorage.setItem("token", newToken);
            localStorage.setItem("refreshToken",newRefreshToken);
            if (newToken && newRefreshToken == null){
              return false
            }
            else {
              return true;
            }
  }

Startup.cs

 services.AddAuthentication(opt =>
            {
                opt.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                opt.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
            }).AddJwtBearer(options =>
            {
                options.TokenValidationParameters = new TokenValidationParameters
                {
                    ValidateIssuer = true,
                    ValidateAudience = true,
                    ValidateLifetime = true,
                    ValidateIssuerSigningKey = true,

                    ValidIssuer = jwtSettings.ValidIssuer,
                    ValidAudience = jwtSettings.ValidAudience,
                    IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration[ConfigurationKeyConstant.JWT_SECRET_KEY])),
                    ClockSkew = TimeSpan.Zero
                };
            });

Controller.cs

[Route("refresh")]
        public IActionResult Refresh(AuthTokenBase authTokenBase)
        {
            if (authTokenBase is null)
            {
                return BadRequest("Invalid client request");
            }
            string accessToken = authTokenBase.Token;
            string refreshToken = authTokenBase.RefreshToken;
            var principal = this._jwtHandler.GetPrincipalFromExpiredToken(accessToken);
            var username = principal.Identity.Name; 
            string newAccessToken = this._jwtHandler.GenerateToken(); 
            string newRefreshToken = this._jwtHandler.GenerateRefreshToken();
            return Ok(new AuthToken()
            {
                Token = newAccessToken,
                RefreshToken = newRefreshToken,
                IsAuthSuccessful = true
            });
        }
1
We use a timer observable to refresh before the token expires, but it gets complicated when there are calls going out during the refresh. Why do you want to do this? Why isn't the default implementation good? - MotKohn
@MotKohn as you mention timer observable in went on web and found there is time interval so I use it in my code import { interval, Observable, Subscription } from "rxjs"; timer: any; constructor(private _authservice: AuthService, private _storageService: StorageService) { } ngOnInit() { this.timer = interval(60000).subscribe(x => { this._authservice.isUserAuthenticated(); }); is this right way ?? - Shivam Dubey
That is what I meant, but again: What are you going to do about calls that are happening while you invalidated the old token and did not yet get back the new one? That is why the authguard is implemented the way it is. - MotKohn
@MotKohn As I am not setting ClockSkew=TimeSpan.Zero in Startup.cs => Configure in asp.net-core so even after my token will expire server will allow that till 5 minutes which is default ClockSkew. As I am keeping my JWT token expiry time for 5 minutes and keeping time-interval to check validity is 4 minutes .So I think in this way my call will always success - Shivam Dubey

1 Answers

-1
votes

You can use angular interceptor. Intercept every error in http request

intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        var modifiedRequest = this.normalizeRequestHeaders(request);
        return next.handle(modifiedRequest)
            .pipe(
                catchError(error => {
                    if (error instanceof HttpErrorResponse && error.status === 401) {
                        return this.tryGetRefreshTokenService(request, next, error);
                    } else {
                        return this.handleErrorResponse(error);
                    }
                }),
                switchMap((event) => {
                    return this.handleSuccessResponse(event);
                })
            );
}