In this article, we will implement authentication routing guards and also implement interceptors to invoke the refresh token API.
Route Guards:
In our current sample, we have an issue with the navigation that is like after login user can access the login page which is not correct, and without login can access the 'fav-movies' page. So we can correct this issue by integrating the routing guards.
Let's create a routing guard service like 'AuthGuard'.
ng generate class shared/auth/auth-guard --skip-tests
src/app/shared/auth/auth-guard.ts:
import { Injectable } from '@angular/core'; import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot, UrlTree, } from '@angular/router'; import { Observable } from 'rxjs'; import { AuthService } from './auth.service'; @Injectable() export class AuthGuard implements CanActivate { constructor(private authService: AuthService, private router: Router) {} canActivate( route: ActivatedRouteSnapshot, state: RouterStateSnapshot ): | boolean | UrlTree | Observable<boolean | UrlTree> | Promise<boolean | UrlTree> { this.authService.getAccessToken(); var userProfile = this.authService.userProfile.getValue(); if ((userProfile?.sub ?? 0) > 0) { if (route.data['requiredAuth'] == false) { this.router.navigate(['/']); return false; } return true; } else { if (route.data['requiredAuth'] == true) { this.router.navigate(['/']); return false; } return true; } } }
- (Line: 25) The 'this.authService.getAccessToken()' method invoked here to refresh the 'UserProfile' observable.
- (Line: 26) Fetching the user profile information.
- (Line: 28-35) If the User is authenticated. Then check the route data value like 'requiredAuth'. If the user authenticated and 'requiredAuth' is 'false' then navigate back to the home page.
- (Line: 35-41) If the user is not authenticated and then 'requiredAuth' is true then also redirect the back user to the home page. So this technique is called guarding the routes.
src/app/app-routing.module.ts:
import { AuthGuard } from './shared/auth/auth-guard'; // existing code hidden for display purpose const routes: Routes = [ { path: '', component: HomeComponent, canActivate:[AuthGuard] }, { path: 'auth', loadChildren: () => import('./auth/auth.module').then((_) => _.AuthModule), }, { path: 'movies', loadChildren: () => import('./movies/movies.module').then((_) => _.MoviesModule), }, ];src/app/auth/auth-routing.module.ts:
import { AuthGuard } from '../shared/auth/auth-guard'; //existing code hidden for display purpose const routes: Routes = [ { path: 'login', component: LoginComponent, data: { requiredAuth: false, }, canActivate: [AuthGuard], }, ];src/app/movies/movies-routing.module.ts:
import { AuthGuard } from '../shared/auth/auth-guard'; // existing code hidden for display purpose const routes: Routes = [ { path: 'fav-movies', component: FavMovieComponent, data:{ requiredAuth: true }, canActivate:[AuthGuard] }, ];Finally in 'AppModule' in the 'providers' array register the 'AuthGuard'.
src/app/app.module.ts:
import { AuthGuard } from './shared/auth/auth-guard'; // existing code hidden for display purpose @NgModule({ providers: [AuthGuard] }) export class AppModule { }
Refresh Token:
- Refresh Token is a random string key that will be created along with the JWT access token and returned to the valid client on successful logging in.
- Now for all subsequent request will use the access token, but the access token is a short-lived token whereas the refresh token livers more time than the access token.
- On the expiration of the access token the user instead of authenticating himself again passing his or her user name and password, the user can send the refresh token.
- The server on receiving a refresh token first validates against the storage(database, cache, etc).
- For a valid refresh, the token server will create the new access token and refresh token and return it to the user.
Implement Interceptor To Invoke the Refresh Token API Call:
Let's implement the refresh token API call.
src/app/shared/auth/auth.service.ts:
refreshToken(payload: TokenModel) { return this.httpClient.post<TokenModel>( 'http://localhost:3000/auth/refreshtoken', payload ); }
- Here invoking the refresh token API call.
ng generate class shared/auth/auth-token-interceptor --skip-tests
src/app/shared/auth/auth-token-interceptor.ts:
import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest, } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { Router } from '@angular/router'; import { JwtHelperService } from '@auth0/angular-jwt'; import { Observable, switchMap, throwError } from 'rxjs'; import { AuthService } from './auth.service'; import { TokenModel } from './token-model'; import { UserProfile } from './user-profile'; @Injectable() export class AuthTokenInterceptor implements HttpInterceptor { constructor( private jwtHelper: JwtHelperService, private authService: AuthService, private router: Router ) {} intercept( req: HttpRequest<any>, next: HttpHandler ): Observable<HttpEvent<any>> { if (req.url.indexOf('login') > -1 || req.url.indexOf('refreshtoken') > -1) { return next.handle(req); } const localStorageTokens = localStorage.getItem('tokens'); var token: TokenModel; if (localStorageTokens) { token = JSON.parse(localStorageTokens) as TokenModel; var isTokenExpired = this.jwtHelper.isTokenExpired(token?.access_token); if (!isTokenExpired) { return next.handle(req); } else { return this.authService.refreshToken(token).pipe( switchMap((newTokens: TokenModel) => { localStorage.setItem('tokens', JSON.stringify(newTokens)); var userInfo = this.jwtHelper.decodeToken( newTokens.access_token ) as UserProfile; this.authService.userProfile.next(userInfo); const transformedReq = req.clone({ headers: req.headers.set( 'Authorization', `bearer ${newTokens.access_token}` ), }); return next.handle(transformedReq); }) ); } } this.router.navigate(['/']); return throwError(() => 'Invalid call'); } }
- (Line: 26-28) API calls like 'login' & 'refreshtoken' will not modify by the interceptor.
- (Line: 30) Fetching the tokens from the browser's local storage.
- (Line: 34-37) If the token is active then nothing will be modified by the interceptor.
- (Line: 38) Invoking the refresh token API.
- (Line: 39-50) After receiving the token API response, save the new token into the browser's local storage. Re invoking the failed API call with the new access token.
src/app/app.module.ts:
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http'; import { AuthTokenInterceptor } from './shared/auth/auth-token-interceptor'; // existing code hidden for display purpose @NgModule({ providers: [ { provide: HTTP_INTERCEPTORS, useClass: AuthTokenInterceptor, multi: true, }, ] }) export class AppModule {}
Support Me!
Buy Me A Coffee
PayPal Me
Video Session:
Wrapping Up:
Hopefully, I think this article delivered some useful information on the Angular 14 Jwt Access Token Authentication and Refresh Token. using I love to have your feedback, suggestions, and better techniques in the comment section below.
Comments
Post a Comment