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

  1. Add JWT Token to Angular HTTP Requests Using NGRX

  2. Angular 7 Login and Registration with JWT Node Authentication

  3. Angular 7 Authenticated Route Guard

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.

Code is not production grade and is ONLY meant for explanation purposes.

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 falsetrue 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:

  1. Implement CanActivate interface method canActivate() and return a Boolean Observable:

    public canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean>
  2. Use a selector function on the auth store to determine if the user is authenticated.

  3. 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:

  1. Create a wrapper module RouteGuardModule to provide any and all route guards in our application. This way we can simply import and export the entire RouteGuardModule into our CoreModule and get all of the guards as injectable services without importing them all over the place.

  2. Next we’ll actually use the route guard by adding it to the beer route’s canActivate property; open up app-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. )

auth-route-guard-not-logged-in-log-msg.png

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. )

auth-route-guard-success-log-msg.png

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:

  1. Create the route guard and implement the method canActivate() and return a Boolean Observable.

  2. Use NGRX selectors to determine if the user is authenticated in the route guard.

  3. Route the user to login if not authenticated.

  4. Create a wrapper module RouteGuardModule to contain all route guards and import and export the wrapper module in CoreModule.

  5. Add the AuthRouteGuard to protected routes using the canActivate property.

See the code in action by grabbing the GitHub repo for the application.

Comment