Creating a Secure Role-Based App Using Angular Route Guard

Jay - May 6 '20 - - Dev Community

While creating a web app, setting up permissions based on roles is a common scenario. Some users may have permissions to certain pages or certain sections of the pages depending on their user role.

In this tutorial, you'll learn how to create a role-based app using Angular route guard. The source code from this tutorial is available at GitHub.

Setting Up the App

Let's start by setting up the Angular app. Assuming that you already have the Angular CLI installed, let's create a new Angular application.

ng new angular-role-app

The above command creates the basic boilerplate code for the Angular application. Navigate to the angular project folder and start the application.

cd angular-role-app
npm start

You will have the Angular app running at localhost:4200.

Creating Angular Modules

Here are the three sections of our Angular application and their permissions:

  • Administration section - accessible only by a superuser
  • Management section - accessible only by a manager
  • General section - accessible by any user

Let's create a module for each of the above sections. Create the Admin module by using the following Angular CLI command:

ng g module Administration --routing

Add two components to the administration module, admin and adminHome.

ng g component administration/admin
ng g component administration/adminHome

Let's also define the routing for the administration module as shown:

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { AdminComponent } from './admin/admin.component';
import { AdminHomeComponent } from './admin-home/admin-home.component';

const routes: Routes = [
  { path: '', children :[
    { path : 'adminHome', component : AdminHomeComponent },
    { path : 'admin', component : AdminComponent },
    { path : '', redirectTo : 'admin', pathMatch : 'full' }
  ] }
];

@NgModule({
  declarations: [],
  imports: [RouterModule.forChild(routes)],
  exports : [RouterModule]
})
export class AdminRoutingModule { }

Similarly, create the management and general section module using the command below. (The --routing option creates the routing file for each of the consecutive modules.)

ng g module general --routing
ng g module management --routing

Create a dashboard component inside the general module.

ng g component general/dashboard

Define the routing for the general module as shown:

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { DashboardComponent } from './dashboard/dashboard.component';

const routes: Routes = [
  { path: '', component: DashboardComponent }
];

@NgModule({
  declarations: [],
  imports: [RouterModule.forChild(routes)],
  exports: [RouterModule]
})

export class GeneralRoutingModule { }

Create a component called “management” inside the management module.

ng g component management/management

Define the routing file for the management module as below:

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { ManagementComponent } from './management/management.component';

const routes: Routes = [
  { path: '', component: ManagementComponent }
];

@NgModule({
  declarations: [],
  imports: [RouterModule.forChild(routes)],
  exports: [RouterModule]
})

export class ManagementRoutingModule { }

To enable the user to log in, let's also create a Login component.

ng g component login

Now you have the required modules and components ready, also define the routing module for the Angular application in the app-routing.module.ts file:

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { LoginComponent } from './login/login.component';

const routes: Routes = [
  { path: 'login', component: LoginComponent },
  { 
    path: 'admin', 
    loadChildren: () => import('./administration/administration.module').then(m => m.AdministrationModule) 
  },
  { 
    path: 'general', 
    loadChildren: () => import('./general/general.module').then(m => m.GeneralModule) 
  },
  { 
    path: 'manage', 
    loadChildren: () => import('./management/management.module').then(m => m.ManagementModule) 
  },
  { path: '', redirectTo : 'login', pathMatch:'full' }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

As seen in the above routing module routes, LoginComponent is the default component.

Let's implement the logic for the Login component which we'll use for securing our Angular application routes.

Protect your Angular App with Jscrambler

Implementing the Login Component

Let's start by implementing the Login component which will authenticate the user. For the sake of this tutorial, you'll be hard coding the authentication process inside the Login component. This login process is for demonstration purposes only and should not be used in production apps.

You'll be using Bootstrap for styling the HTML pages. Install it on the Angular app using the following command:

npm install bootstrap jquery

Once done with the dependency installation, add the following style and script to the angular.json file:

"styles": [
    "src/styles.css",
    "node_modules/bootstrap/dist/css/bootstrap.min.css"
],
"scripts": [
    "node_modules/jquery/dist/jquery.min.js",
    "node_modules/bootstrap/dist/js/bootstrap.min.js"
]

Also add the following HTML code to the login.component.html file:

<form class="form-signin">
    <img class="mb-4" src="/docs/4.4/assets/brand/bootstrap-solid.svg" alt="" width="72" height="72">
    <h1 class="h3 mb-3 font-weight-normal">Please sign in</h1>
    <label for="inputUsername" class="sr-only">Username</label>
    <input type="text" id="inputUsername" name="username" [(ngModel)]="username" class="form-control" placeholder="Username" required autofocus>
    <label for="inputPassword" class="sr-only">Password</label>
    <input type="password" id="inputPassword" name="password" [(ngModel)]="password" class="form-control" placeholder="Password" required>

    <button class="btn btn-lg btn-primary btn-block" (click)="handleLoginClick()" type="button">Sign in</button>
  </form>

And add the following code to the login.component.ts file:

import { Component, OnInit } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Router } from '@angular/router';

@Component({
  selector: 'app-login',
  templateUrl: './login.component.html',
  styleUrls: ['./login.component.css']
})
export class LoginComponent implements OnInit {

  username;
  password;

  constructor(private http : HttpClient, private router : Router) { }

  ngOnInit(): void {

  }

  handleLoginClick(){
    if(this.username && this.password){
      this.authenticateUser(this.username);
    } else {
      alert('enter username and password');
    }

  }

  authenticateUser(userName){
    if(userName == "admin"){
      this.router.navigate(['/admin']);
    } else if(userName == "manager"){ 
      this.router.navigate(['/manage']);
    } else if(userName == "general"){
      this.router.navigate(['/general'])
    }
  }

}

Note: Authenticating using username as shown above is not a secure way of authentication. This is for demo purposes only. For more information on secure authentication, check this tutorial.

As seen in the authenticateUser method in login.component.ts, if the user role is admin, manager, or general user, he'll be redirected to the specific module.

Save the above changes and run the application. Try signing in as admin and you'll be redirected to the admin module.

Securing Routes Using Angular Route Guard

There is an issue with the above implementation. The routes are not secure. If you log in as a general user and try to access localhost:4200/admin, the route will display the admin module. So how can we secure the routes for unauthorized access?

First, you need to store the user information somewhere to identify the user. Let's keep the logged in user information in session storage.

Inside the authenticateUser method in login.component.ts, add the following line of code to keep the user information in session storage:

sessionStorage.setItem("user", userName);

Here is how the authenticateUser method looks:

  authenticateUser(userName){
    sessionStorage.setItem("user", userName);
    if(userName == "admin"){
      this.router.navigate(['/admin']);
    } else if(userName == "manager"){ 
      this.router.navigate(['/manage']);
    } else if(userName == "general"){
      this.router.navigate(['/general'])
    }
  }

Now, create a service called routeGuard using the Angular CLI command :

ng g service routeGuard

In this Angular service, you'll implement the Angular CanActivate guard interface to secure the Angular route.

Here is how the route-guard.service.ts file looks :

import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot } from '@angular/router';

@Injectable({
  providedIn: 'root'
})
export class RouteGuardService implements CanActivate {

  constructor() { }

  public canActivate(route: ActivatedRouteSnapshot){
    return true;
  }
}

Add the above RouterGuardService to the routes defined in the app-routing.module.ts file's admin route.

{ 
    path: 'admin', 
    canActivate : [RouteGuardService],
    loadChildren: () => import('./administration/administration.module').then(m => m.AdministrationModule) 
}

Here is how the app-routing.module.ts file looks :

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { LoginComponent } from './login/login.component';
import { RouteGuardService } from './route-guard.service';

const routes: Routes = [
  { path: 'login', component: LoginComponent },
  { 
    path: 'admin', 
    canActivate : [RouteGuardService],
    loadChildren: () => import('./administration/administration.module').then(m => m.AdministrationModule) 
  },
  { 
    path: 'general', 
    loadChildren: () => import('./general/general.module').then(m => m.GeneralModule) 
  },
  { 
    path: 'manage', 
    loadChildren: () => import('./management/management.module').then(m => m.ManagementModule) 
  },
  { path: '', redirectTo : 'login', pathMatch:'full' }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

The canActivate method in RouteGuardService returns true at the moment. Let's add the logic to only allow access to the admin route by admin users.

  public canActivate(route: ActivatedRouteSnapshot){
    let user = sessionStorage.getItem('user');
    if(user == 'admin'){
      return true;
    }
    return false;
  }

You can get the user information from the session storage and return true if the user is admin, else false.

Save the above changes and restart the Angular app. Log in as a general user and try to access the localhost:4200/admin/ route. You will be redirected to the Login page.

Similarly, using Angular guards you can secure any of the other routes.

Wrapping It Up

In this tutorial, you learned how to secure an Angular app route using route guards. Here, we only explored the Angular guards concept from a PoC point of view.

While implementing Angular guards, you can use other interfaces that Angular provides. To get an in-depth understanding, we recommend reading the Angular route guard's documentation.

And since we're talking about security, learn how you can obfuscate and protect the source code of your Angular apps by following our guide.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .