Securing Angular Applications

Single Page Applications or SPA have become the gold standard for modern web applications. Almost every application contains two major areas: secured and unsecured. Today we will learn how to restrict access to the secured area for non-authorized users in modern Angular (version 2 and higher).
No previous Angular experience is required to complete this tutorial. Chrome browser is recommended. You will be able to download complete source code at the end.

Prerequisites

  1. NodeJS. You can download latest Node from https://nodejs.org/en/download/
  2. Angular CLI. Once you have installed Node, open the terminal and run 
    npm install angular-cli -g

    If you are getting access errors on Mac run the same script with sudo. 

Setup

Let’s start by creating a new project. Open the terminal and navigate to the place where you want to create your project. Run the following command to generate a basic Angular app:

ng new secure-angular-client-side

Your app will be generated in the secure-angular-client-side folder. Navigate to it and run your app:

cd secure-angular-client-side
ng serve

ng serve will start a simple development server on localhost:4200. It will also watch for any code changes and refresh your browser automatically on each change. If you open https://localhost:4200 in your browser you should see this:

At this point you can open the project in your favorite IDE and we can move to the next part, which is generating components for our application.

Components

We will generate 3 components for our application:

  1. Login component. This component will have a simple login form for the demonstration.
  2. Shell component. This component will be our app master page and will hold the layout, such as header / footer and content area.
  3. Dashboard component. This component will represent the application dashboard which should be accessible only by logged in users.

Angular CLI provides us with an easy way to generate components. Run the following commands in the terminal from the root folder of your project:

ng g component login --skip-import
ng g component shell --skip-import
ng g component dashboard --skip-import

Your components are now created under the app folder. Let’s create a components folder under app and move the newly created components there. At the end your folder structure should look like this:

Open src/app/app.module.ts and add login, shell and dashboard components to declarations:

import {LoginComponent} from './components/login/login.component';
import {ShellComponent} from './components/shell/shell.component';
import {DashboardComponent} from './components/dashboard/dashboard.component';
...
declarations: [
 AppComponent,
 LoginComponent,
 ShellComponent,
 DashboardComponent
],
...

Now let’s setup routing for our application. We will also add basic navigation between components to verify our routes work.

Routing

Create the file app.routing.module.ts under app folder and paste in the following contents:

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import {LoginComponent} from './components/login/login.component';
import {DashboardComponent} from './components/dashboard/dashboard.component';
import {ShellComponent} from './components/shell/shell.component';
const routes: Routes = [
 { path: '', redirectTo: 'login', pathMatch: 'full' },
 {
   path: 'app',
   component: ShellComponent,
   children: [
     {path: 'dashboard', component: DashboardComponent},
   ]
 },
 { path: 'login', component: LoginComponent },
 { path: '**', redirectTo: '/login'},
];
@NgModule({
 imports: [RouterModule.forRoot(routes)],
 exports: [RouterModule],
 providers: []
})
export class AppRoutingModule { }

Go to src/app/app.module.ts and add AppRoutingModule and FormsModule to imports:

...
imports: [
 BrowserModule,
 FormsModule,
 AppRoutingModule
],
...

Open src/app/app.component.html and replace it’s contents with the following HTML:

<ul>
 <li>
   <a [routerLink]="['login']">Login</a>
 </li>
 <li>
   <a [routerLink]="['app/dashboard']">Dashboard</a>
 </li>
</ul>
<router-outlet></router-outlet>

app.component is a root component and it has a <router-outlet> where our top-level components will be mounted and some temporary navigation to test that routing works properly.
Now open src/app/components/shell/shell.component.html and replace it contents with:

<div>HEADER</div>
<router-outlet></router-outlet>

This is our master page and it will have a header and a <router-outlet> where our child components like DashboardComponent under ShellComponent will be mounted. See src/app/app.routing.module.ts for reference:

...
{
   path: 'app',
   component: ShellComponent,
   children: [
     {path: 'dashboard', component: DashboardComponent},
   ]
 },
...

Restart ng serve and verify that you can navigate between dashboard and login with the top menu.

Before we will move to the Authorization part, we will add styling for our login page and shell. Copy and paste the following HTML and CSS snippets into the corresponding files.
src/app/components/login/login.component.html

<div class="login">
 <div class="login__form">
   <div class="login-field">
     <label class="login-field__label">Username</label>
     <input type="text" class="login-field__input"/>
   </div>
   <div class="login-field">
     <label class="login-field__label">Password</label>
     <input type="password" class="login-field__input"/>
   </div>
   <button class="login__button">Sign In</button>
 </div>
</div>

src/app/components/login/login.component.css

.login__form {
 width: 400px;
 padding: 20px;
 box-sizing: border-box;
 background: #fff;
 border: 1px solid #eee;
 margin: 120px auto 0 auto;
 font-family: Arial, sans-serif;
}
.login-field {
 margin-bottom: 20px;
}
.login-field__label {
 display: block;
 color:#777;
 font-size: 14px;
 margin-bottom: 5px;
}
.login-field__input {
 display: block;
 width: 100%;
 box-sizing: border-box;
 border: 1px solid #ccc;
 padding-left: 5px;
 height: 30px;
 font-family: Arial,sans-serif;
 font-size: 14px;
}
.login__button {
 display: block;
 border: none;
 height: 50px;
 color:#fff;
 font-size: 18px;
 background: #ff5800;
 width: 100%;
}

src/app/app.component.html (remove everything except router-outlet)

<router-outlet></router-outlet>

src/app/components/shell/shell.component.html

<header class="header">
 <a class="header__logout">Logout</a>
</header>
<router-outlet></router-outlet>

src/app/components/shell/shell.component.css

.header {
 height: 40px;
 border-bottom: 1px solid #ccc;
}
.header__logout,
.header__logout:visited {
 float:left;
 color:#000;
 text-decoration: underline;
 cursor: pointer;
 font-size: 14px;
 font-family: Arial,sans-serif;
 margin-top: 7px;
}

src/app/components/dashboard/dashboard.component.html

<h1>Welcome to the Dashboard</h1>

src/app/components/dashboard/dashboard.component.css

h1 {
 font-size: 22px;
 font-family: Arial, sans-serif;
 color: #777;
}

Now we need to simulate the login/logout process. We already have a basic login form and a header with a logout button. It’s time to add the AuthService that will be responsible for handling the authorization process and providing the current auth state.

Authorization

Create a services folder under app and create auth.service.ts there. This will be our AuthService. Paste the following contents into that file:

import {Injectable} from '@angular/core';
import {Observable} from 'rxjs/Observable';
@Injectable()
export class AuthService {
 constructor() {}
authorize(username: string, password: string): Observable<boolean> {
 return new Observable<boolean>(observer => {
   localStorage.setItem('token', '123');
   observer.next(true);
   observer.complete();
 });
}
 unauthorize() {
   localStorage.removeItem('token');
 }
 isAuthorized() {
   return !!localStorage.getItem('token');
 }
}

Also add AuthService to the providers array in app.module.ts: 

...
providers: [
 AuthService
],
...

In the real world authorize method would send a request to an API and parse the response. Now let’s wire up authorize() and unauthorize() methods to the Sign In button on the login screen and the Logout link in the header.
Update login.component.ts. 

import { Component, OnInit } from '@angular/core';
import {AuthService} from '../../services/auth.service';
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 authService: AuthService, private router: Router) { }
 ngOnInit() {
 }
 signIn() {
   this.authService.authorize(this.username, this.password).subscribe(
     success => success && this.router.navigate(['app/dashboard'])
   );
 }
}

Here we declared 2 fields: username and password which we will bind in the template using [(ngModel)]. Also we added a signIn() method that will call authService.authorize() and in case of success it will redirect the user to the dashboard. Now open login.component.html and add the bind fields and methods that we just created. At the end login.component.html should have the following code:

<div class="login">
 <div class="login__form">
   <div class="login-field">
     <label class="login-field__label">Username</label>
     <input type="text" [(ngModel)]="username" class="login-field__input"/>
   </div>
   <div class="login-field">
     <label class="login-field__label">Password</label>
     <input type="password" [(ngModel)]="password" class="login-field__input"/>
   </div>
   <button class="login__button" (click)="signIn()">Sign In</button>
 </div>
</div>

Test to see if it works. Go to https://localhost:4200/login. Enter any login and password, you can even leave them blank, press sign in and you should see our dashboard page.
Now let’s update shell.component.ts:

import { Component, OnInit } from '@angular/core';
import {AuthService} from '../../services/auth.service';
import {Router} from '@angular/router';
@Component({
 selector: 'app-shell',
 templateUrl: './shell.component.html',
 styleUrls: ['./shell.component.css']
})
export class ShellComponent implements OnInit {
 constructor(private authService: AuthService, private router: Router) { }
 ngOnInit() {
 }
 logout() {
   this.authService.unauthorize();
   this.router.navigate(['login']);
 }
}

Here we added a logout() method that will call unauthoize() on our authentication service and after that redirect the user back to login. Tweak logout link in shell.component.ts: 

...
<a class="header__logout" (click)="logout()">Logout</a>
...

..and test that you can logout by clicking Logout on dashboard.
Now we have the complete login/logout flow with two major problems:

  1. The user can open dashboard without being logged in by typing dashboard URL in the browser.
  2. The user can open login while logged in by typing login URL in the browser.

Let’s look into how to resolve those issues in an effective way in Angular in the final chapter.

Guards

Guards are a very powerful feature implemented in the latest Angular. Guard is a class that implements one of the interfaces and returns true or false from the corresponding function. There are 5 types of route guards at the moment: CanActivate, CanActivateChild, CanDeactivate, CanLoad and Resolve. In our application we will use CanActivate guard. You can read more about other types of guards from the official Angular documentation: https://angular.io/guide/router#milestone-5-route-guards.
Let’s start by creating a guards folder under app. Then go back to the terminal and run the following commands:

ng g guard auth
ng g guard nonauth

You will see the newly created guards under the app folder. Move them into the guards folder.
Open auth.guard.ts. You can see that the Angular CLI generates a CanActivate guard by the default:

import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs/Observable';
@Injectable()
export class AuthGuard implements CanActivate {
 canActivate(
   next: ActivatedRouteSnapshot,
   state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
   return true;
 }
}

Currently the guard returns true, so if we put it on some route, it will allow access to the route every time. Notice that we can return an Observable or Promise from that function. This is handy when you need to make API call to decide whether the user can access specific route or not, e.g. fetching permissions from the API.
Open the app.routing.module.ts and assign guards to routes in the following way:

...
const routes: Routes = [
 { path: '', redirectTo: 'login', pathMatch: 'full' },
 {
   path: 'app',
   component: ShellComponent,
   children: [
     {path: 'dashboard', component: DashboardComponent},
   ],
   canActivate: [AuthGuard]
 },
 { path: 'login', component: LoginComponent, canActivate: [NonauthGuard] },
 { path: '**', redirectTo: '/login'},
];
...

Note AuthGuard on ShellComponent. You don’t need to assign it to each child as well – just to the parent route. Child routes will be automatically be guarded by the parent and that’s another reason why we want to have the ShellComponent.
Update the providers array in the app.module.ts: 

...
providers: [
 AuthService,
 AuthGuard,
 NonauthGuard
],
...

As a final part let’s add some logic into AuthGuard and NonauthGuard. We need to inject AuthService and Router into both guards and check if the user is authorized in the canActivate function. Depending on that we should either return true allowing him to access the route or redirect him to some page that we know he has access to, e.g. login if user is not authorized or dashboard in the opposite case.
src/app/guards/auth.guard.ts

import { Injectable } from '@angular/core';
import {CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router} from '@angular/router';
import { Observable } from 'rxjs/Observable';
import {AuthService} from '../services/auth.service';
@Injectable()
export class AuthGuard implements CanActivate {
 constructor(private authService: AuthService, private router: Router) {}
 canActivate(
   next: ActivatedRouteSnapshot,
   state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
   if (this.authService.isAuthorized()) {
     return true;
   }
   this.router.navigate(['login']);
   return false;
 }
}

src/app/guards/nonauth.guard.ts

import { Injectable } from '@angular/core';
import {CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router} from '@angular/router';
import { Observable } from 'rxjs/Observable';
import {AuthService} from '../services/auth.service';
@Injectable()
export class NonauthGuard implements CanActivate {
 constructor(private authService: AuthService, private router: Router) {}
 canActivate(
   next: ActivatedRouteSnapshot,
   state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
   if (!this.authService.isAuthorized()) {
     return true;
   }
   this.router.navigate(['app/dashboard']);
   return false;
 }
}

Now we are ready for the final test.
Clean localStorage by opening the browser console and typing localStorage.clear(). Refresh the page. You should land on the login page even if you stayed on the dashboard before. Try to access the dashboard by changing the URL: you will not be able to do this. Every time the app will redirect you back to the login page. Now login to the app and try to go to the login page. Again the app will stay on the dashboard.

Conclusion

This is how you can easily secure any Angular app. Note that guards can be used in different variations, not only to check auth, but also with roles, permissions, etc. You can also have multiple guards on a single route. In that case the route will be activated only if all of them return true.
You can also download the complete example of this app from Github: https://github.com/trailheadtechnology/secure-angular-client-side.git

Related Blog Posts

We hope you’ve found this to be helpful and are walking away with some new, useful insights. If you want to learn more, here are a couple of related articles that others also usually find to be interesting:

Our Gear Is Packed and We're Excited to Explore With You

Ready to come with us? 

Together, we can map your company’s software journey and start down the right trails. If you’re set to take the first step, simply fill out our contact form. We’ll be in touch quickly – and you’ll have a partner who is ready to help your company take the next step on its software journey. 

We can’t wait to hear from you! 

Main Contact

This field is for validation purposes and should be left unchanged.

Together, we can map your company’s tech journey and start down the trails. If you’re set to take the first step, simply fill out the form below. We’ll be in touch – and you’ll have a partner who cares about you and your company. 

We can’t wait to hear from you! 

Montage Portal

Montage Furniture Services provides furniture protection plans and claims processing services to a wide selection of furniture retailers and consumers.

Project Background

Montage was looking to build a new web portal for both Retailers and Consumers, which would integrate with Dynamics CRM and other legacy systems. The portal needed to be multi tenant and support branding and configuration for different Retailers. Trailhead architected the new Montage Platform, including the Portal and all of it’s back end integrations, did the UI/UX and then delivered the new system, along with enhancements to DevOps and processes.

Logistics

We’ve logged countless miles exploring the tech world. In doing so, we gained the experience that enables us to deliver your unique software and systems architecture needs. Our team of seasoned tech vets can provide you with:

Custom App and Software Development

We collaborate with you throughout the entire process because your customized tech should fit your needs, not just those of other clients.

Cloud and Mobile Applications

The modern world demands versatile technology, and this is exactly what your mobile and cloud-based apps will give you.

User Experience and Interface (UX/UI) Design

We want your end users to have optimal experiences with tech that is highly intuitive and responsive.

DevOps

This combination of Agile software development and IT operations provides you with high-quality software at reduced cost, time, and risk.

Trailhead stepped into a challenging project – building our new web architecture and redeveloping our portals at the same time the business was migrating from a legacy system to our new CRM solution. They were able to not only significantly improve our web development architecture but our development and deployment processes as well as the functionality and performance of our portals. The feedback from customers has been overwhelmingly positive. Trailhead has proven themselves to be a valuable partner.

– BOB DOERKSEN, Vice President of Technology Services
at Montage Furniture Services

Technologies Used

When you hit the trails, it is essential to bring appropriate gear. The same holds true for your digital technology needs. That’s why Trailhead builds custom solutions on trusted platforms like .NET, Angular, React, and Xamarin.

Expertise

We partner with businesses who need intuitive custom software, responsive mobile applications, and advanced cloud technologies. And our extensive experience in the tech field allows us to help you map out the right path for all your digital technology needs.

  • Project Management
  • Architecture
  • Web App Development
  • Cloud Development
  • DevOps
  • Process Improvements
  • Legacy System Integration
  • UI Design
  • Manual QA
  • Back end/API/Database development

We partner with businesses who need intuitive custom software, responsive mobile applications, and advanced cloud technologies. And our extensive experience in the tech field allows us to help you map out the right path for all your digital technology needs.

Our Gear Is Packed and We're Excited to Explore with You

Ready to come with us? 

Together, we can map your company’s tech journey and start down the trails. If you’re set to take the first step, simply fill out the contact form. We’ll be in touch – and you’ll have a partner who cares about you and your company. 

We can’t wait to hear from you! 

Thank you for reaching out.

You’ll be getting an email from our team shortly. If you need immediate assistance, please call (616) 371-1037.