0

Is it a good idea to call a server in the guard in order to verify if jwt is valid?

I'm storing jwt on the frontend, but I don't know if that token is expired or blacklisted unless I contact the backend. Also, I don't know the user's role, which is important to me for AdminGuard.

The first idea I have had is just to simply call the backend, and with that approach, I'll have all the necessary information, but the main concern of that approach is that every time route is changed, I'll need http call.

After some digging, I found a library for angular, to verify jwt. And about roles, I could just save the user's role as a payload, and then decode it with this jwt library. The blacklisted tokens might be a problem, but I think that is a rare scenario, and even if the guard lets that user continues on a certain route, my server will just return an error on the first request and I'll redirect.

Should I try the second approach? Or http calls is not a big deal in guards?

while1618
  • 53
  • 7

1 Answers1

0

Now here's the thing, if you want to follow the best practices, i would recommend using an interceptor to verify 401 Unauthorized errors (that's basically when tokens are expired). and in that interceptor you can call the login method from your auth service to refresh your token and retry the request.

Here's a sample code:

import { Injectable } from '@angular/core';
import {
  HttpRequest,
  HttpHandler,
  HttpInterceptor,
  HttpErrorResponse,
} from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { catchError, retry } from 'rxjs/operators';
import { Router } from '@angular/router';
import { StorageService } from 'src/app/services/storage.service';
import { AuthService } from 'src/app/modules/auth/auth.service';
import { User } from 'src/app/models/entities/user.interface';
import { ToastService } from 'src/app/services/toast.service';
import { NgxSpinnerService } from 'ngx-spinner';
@Injectable()
export class ErrorInterceptor implements HttpInterceptor {
  constructor(
    private _authService: AuthService,
    private _storageService: StorageService,
  ) {}
  //!Global scope error handling
  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<any> {
    return next.handle(request).pipe(
      // -> retry(1),
      catchError((error: HttpErrorResponse) => {
        if (error.error instanceof ErrorEvent) {
          // handle client-side error
        } else {
          // handle server-side error
          //-> i.e. 401, 404, 500, etc..
          //-> Also refresh token for expired token
          switch (error.status) {
            case 401: //login
              this._authService
                .postLogin(this._storageService.getLocalObject('credentials'))
                .subscribe((user: User) => {
                  this._storageService.setLocalObject('user', user);
                  this._storageService.setToken(user.jwtToken);
                });
              break;
            case 403: //forbidden
              break;
            case 404: //not found
              break;
            case 500: //internal server error
              break;
          }
        }
        return throwError(error);
      })
    );
  }
}

Keep everything seperated in order to increase reusability. So it's a no for calling an HTTP request in your Guard.

Mohamed Karkotly
  • 1,364
  • 3
  • 13
  • 26