Overview
This post builds on the last post Angular 7 Login and Registration with JWT Node Authentication and will ultimately turn into “Part 3” of a series:
Series Table of Contents
In this post we’ll add a route guard that only allows the user to navigate to the /beer
route if they’re authenticated. The new AuthRouteGuard
will determine if the user is authenticated via a NGRX store using a selector that checks for the existence of a JWT.
If you want to skip the explanation and just start playing with the app, head on over to the GitHub repo for the application.
Assumptions
This post assumes familiarity with Angular and Angular CLI, Angular Material, TypeScript, JWTs, RxJS, NGRX (or possibly Redux), Smart Container and Dumb / Presentation Component, Basic Angular Routing, Node.js, Express, and json-server.
At the time of writing this my system leveraged the following versions:
Node.js: 10.14.2
NPM: 6.4.1
Angular: 7.0.3
Angular CLI: 7.0.5
OS: MacOS High Sierra v10.13.6
The rest of the versions can easily be looked up in the package.json.
We could enable the auth check by using LocalStorage
to persist sessions, but that’s not only a security flaw it’s out of scope for this post. We’ll keep it simple and determine if the user is authenticated for a given session if the token exists in the store.
Quick Start
Let’s clone the repo and fire up the app so we can see it in action. Open up a terminal and enter the following commands:
wasi$ git clone https://github.com/webappsolution/angular-add-auth-token-ngrx.git wasi$ git checkout feature/step-2-auth-routing-guard wasi$ npm i wasi$ npm run dev-auth
The last command will concurrently start both the server and client — the server uses json-server to quickly scaffold an in-memory JSON database, while the client is the Angular app running via Angular CLI. Developers can also choose to run the client and sever in 2 separate terminals in case you like reading the console a bit easier:
wasi$ npm run client wasi$ npm run server-auth
Creating the Route Guard
The new AuthRouteGuard
is a simple class that implements the native Angular CanActivate
class and returns either a Boolean value of true
or false
— true
allows the user to user to continue to the requested route whereas false
stops Angular’s routing engine and maintains the current route. In our case we’ll return false
and route the user back to /login
. Let’s take a deeper look at the AuthRouteGuard
implementation.
AUTH.ROUTE-GUARD.TS
Let’s focus on a couple key points for the authentication route guard:
Implement
CanActivate
interface methodcanActivate()
and return a Boolean Observable:public canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean>
Use a selector function on the auth store to determine if the user is authenticated.
Route the user to
/login
if they aren’t authenticated.
import { Injectable } from "@angular/core"; import { ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot } from "@angular/router"; import { select, Store } from "@ngrx/store"; import { Observable } from "rxjs"; import { first, map } from "rxjs/operators"; import * as fromState from "../../core/state/"; import * as AuthActions from "../state/auth/auth.action"; @Injectable() export class AuthRouteGuard implements CanActivate { constructor(private store$: Store<any>) {} /** * Determines if the user is authenticated and can navigate to protected routes. * * If the user isn't authenticated, then route them to the login view. * * @returns {Observable<boolean>} */ public canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> { return this.checkStoreAuthentication().pipe( map((authed) => { if (!authed) { this.store$.dispatch(new AuthActions.NavigateToLogin()); console.log(`canActivate( No. Redirect the user back to login. )`); return false; } console.log(`canActivate( Yes. Navigate the user to the requested route. )`); return true; }), first() ); } /** * Determine if the user is logged by checking the Redux store. */ private checkStoreAuthentication(): Observable<boolean> { return this.store$.pipe(select(fromState.getIsLoggedIn)).pipe(first()); } }
Integrate AuthRouteGuard with the Application
Next we’ll need to integrate the route guard with our application using the following:
Create a wrapper module
RouteGuardModule
to provide any and all route guards in our application. This way we can simply import and export the entireRouteGuardModule
into ourCoreModule
and get all of the guards as injectable services without importing them all over the place.Next we’ll actually use the route guard by adding it to the beer route’s
canActivate
property; open upapp-routing.module.ts
and you’ll see the following addition:
////////////////////////////////////////////////// // Protected Routes ////////////////////////////////////////////////// { path: appRoutePaths.beer, loadChildren: "./beer/beer.module#BeerModule", canActivate: [AuthRouteGuard] },
At this point you’re new route guard is all set to use within the application, so let’s see it in action.
Test and See AuthRouteGuard in Action
Go back to the browser and fire up Chrome DevTools’ console and enter in the URL http://localhost:4300/beer — your console should log the following message indicating the user is not authenticated:
canActivate( No. Redirect the user back to login. )
This should also route you to the login page, so enter the following credentials:
username: tom.brady@patriots.com
password: goat
You should be successfully authenticated and routed to the protected /beer
route. Check Chrome DevTools’ console again and you should now see
canActivate( Yes. Navigate the user to the requested route. )
If you reload the page you’ll notice that the user’s in-memory authentication in the NGRX store is gone and thus the routing to /beer
fails, so the user is again routed back to /login
. There are several solutions to this, but they are outside the scope of this post.
Conclusion
In this tutorial we saw how to create an Angular authentication route guard. Key points to the example are:
Create the route guard and implement the method
canActivate()
and return a Boolean Observable.Use NGRX selectors to determine if the user is authenticated in the route guard.
Route the user to login if not authenticated.
Create a wrapper module
RouteGuardModule
to contain all route guards and import and export the wrapper module inCoreModule
.Add the
AuthRouteGuard
to protected routes using thecanActivate
property.
See the code in action by grabbing the GitHub repo for the application.