Angular: Route Authentication and Guards

There will be times in your web application where you will need to protect parts of your application from unauthorized access, for example, if you only want registered users to have access to particular components of your app while denying the unregistered users and re-routing them to a sign up page. This can be accomplished using Angular’s route guards and user authentication.

Before we get started lets have a little recap on the basic concepts of routing and how it works.

Routing

In a web application, user accessible components are split into their own areas.

For example if we visit the / path that will show us the home page and if  that site has an about page we will navigate to /about that will give us the about page and so on.

Why Routing Is Important

Routing is a fundamental concept of any web application and defining them are useful because they:

  • Make our app easy to navigate by seperating it into different areas
  • Maintains the current application state
  • Protects specified areas based on rules

To explain what I mean when I say routes keep the current state of our application, client side web applications do not actually need the URL to be changed when we visit a route but this will not keep its state for example:

  • The user cant refresh the page without staying on the current route
  • You cant share the URL with others
  • You cant bookmark for later viewing
  • No entry point besides the initial index

So from looking at the disadvantages of having a route less web application we can automatically see the positives and benefits that having routes gives us in our application.

Now that we have a brief overview of what routes are and the benefits they offer us, lets get started on looking at guards and authentication.

There are two components to route protection these are the:

  • Authentication service which is responsible for authenticating users by checking their credentials
  • Guard which grants or denies access to the protected route

Lets say we have a signup route a login route and a protected route. The protected route can only be accessed if the user has entered the correct login information. We can do this by assessing the routers life cycle hook which will tell us when the protected route is being accessed. We then use the guard to ask the authentication service if the login credentials are correct and if so access is granted to the protected route else access is denied and the user is routed to the signup page.

Lets have a look at how we can set this up.

Here we define our routes in our global app.routes.ts file:

/**
 * Created by keilc on 3/03/2017.
 */
import {provideRouterInitializer} from "@angular/router/src/router_module";
import {RouterModule, Routes} from "@angular/router";
import {SignupComponent} from "../components/unprotected/signup/signup.component";
import {SigninComponent} from "../components/unprotected/signin/signin.component";
import {ProtectedComponent} from "../components/protected/protected.component";

export const APP_ROUTES: Routes = [
  {path: '', redirectTo: '/signin', pathMatch: 'full'},
  {path: 'signup', component: SignupComponent},
  {path: 'signin', component: SigninComponent},
  {path: 'protected', component: ProtectedComponent}
];

export const routing = RouterModule.forRoot(APP_ROUTES);

As you can see we are defining our signup, signin and protected routes.

Authentication Service

So, now that we have our routes defined (dont forget to import it into the imports array in the applications root module) we can now set up our authentication service which, as the name implies, authenticates the users credentials when they attempt to signin:


import { Injectable } from '@angular/core';
import {User} from '../models/User';
import {FormGroup} from "@angular/forms";
import {Router} from "@angular/router";
declare const firebase: any;

@Injectable()
export class AuthService {

  constructor(private router: Router) { }

  signupUser(user: User){
    firebase.auth().createUserWithEmailAndPassword(user.email, user.password).catch((error) => {
      console.log(error);
      // Handle Errors here.
      const errorCode = error.code;
      const errorMessage = error.message;
      // ...

      console.log('Error Code: ' + errorCode + '\n' + 'Error Message: ' + errorMessage);
    });
  }

  signInUser(user: User){
    firebase.auth().signInWithEmailAndPassword(user.email, user.password).catch(function(error) {
      // Handle Errors here.
      const errorCode = error.code;
      const errorMessage = error.message;
      // ...
      console.log('Error Code: ' + errorCode + '\n' + 'Error Message: ' + errorMessage);
    });
  }

  logOut() {
    firebase.auth().signOut();

    this.router.navigate(['/signin']);
  }

  isAuthenticated() {
    const user = firebase.auth().currentUser;

    if (user) {
      return true;
    } else {
      return false;
    }
  }
}

This is a snippet from one of my applications hence it is validating the user against Firebase, in your case you could just have static data for testing purposes.

It is important to note that isAuthenticated) is not directly used by the signin form, it is called by the Guard when the protected route is navigated to. We will see how this works next.

Route Guard

A quick refresher so we dont get lost. When the user signs in, the Authentication Service is called by the control on the form, when the user routes to the protected route then the Guard is activated which calls the authentication service and depending on the return value the user is either declined or approved access to the protected route.

Here is what our Guard looks like:

/**
 * Created by keilc on 4/03/2017.
 */

import {Injectable} from "@angular/core";
import {ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot} from "@angular/router";
import {Observable} from "rxjs";
import {AuthService} from "../services/auth.service";
@Injectable()

export class AuthGuard implements CanActivate{
  constructor(private authService: AuthService){}

  canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | boolean{
    return this.authService.isAuthenticated();
  }
}

Here we have a few imports that I will discuss:

  • ActivatedRouteSnapshot holds information about the current activated route which is the route we want to navigate to.
  • RouterStateSnapshot is a data structure that represents the state of the router at any given point in time.
  • CanActivate is an interface inside the angular/router module that allows us to use the current class as a guard.

canActivate() will be called whenever Angular finds a route in which a guard applies by looking at each of the imports above.

Apply The Guard

Finally this is the last thing we need to do and it is super simple!

All we need to do is tell the angular router which routes the guard needs to be applied to, we do this in the routing class we defined near the beginning:

/**
 * Created by keilc on 3/03/2017.
 */
import {provideRouterInitializer} from "@angular/router/src/router_module";
import {RouterModule, Routes} from "@angular/router";
import {SignupComponent} from "../components/unprotected/signup/signup.component";
import {SigninComponent} from "../components/unprotected/signin/signin.component";
import {ProtectedComponent} from "../components/protected/protected.component";
import {AuthGuard} from "./auth.guard";

export const APP_ROUTES: Routes = [
  {path: '', redirectTo: '/signin', pathMatch: 'full'},
  {path: 'signup', component: SignupComponent},
  {path: 'signin', component: SigninComponent},
  {path: 'protected', component: ProtectedComponent, canActivate: [AuthGuard]}
];

export const routing = RouterModule.forRoot(APP_ROUTES);

Can you spot the difference?

We have imported our guard and injecting it into the router on the protected component which will then execute the canActivate method on the guard. We specify the guard in an array because we can have multiple guards.

Now all we need to do is provide the guard in the root module:

providers: [AuthService,
              AuthGuard],

And thats it! Here is a control flow diagram to show how it all works:

Capture

If you would like to know more about routing in Angular check out the docs here

Thankyou for reading and have a nice day/night 🙂

Advertisements

One thought on “Angular: Route Authentication and Guards

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s