Wednesday, September 21, 2022

Angular Access Control CanActivate Route Guard Example

In your Angular application you may need to control access to different parts of your app. To control that kind of authorization and authentication you can use route guards in Angular. You can say that using route guards you can perform some action before navigating to another route. In this post we’ll see example of using CanActivate route guard in Angular for authentication.


Why route guards in Angular

Use of route guards to control access in your Angular app may be considered for following scenarios-

  • User is not authorized to navigate to the component rendered by the route user want to navigate to.
  • User is not authenticated (not logged in).
  • You need to fetch some data before you display the target component.
  • You want to save pending changes before leaving a component.
  • You want to ask the user if it's OK to discard pending changes rather than save them.

You can add route guards to the route configuration to handle the above mentioned scenarios.

Value returned by route guard controls the router's behavior:

  • If it returns true, the navigation process continues.
  • If it returns false, the navigation process stops and the user may remain at the same component or navigated to another route.
  • If it returns a UrlTree, the current navigation cancels and a new navigation is initiated to the UrlTree returned.

Route guard interfaces in Angular

In Angular there are multiple guard interfaces:

  1. CanActivate- to control navigation to a route.
  2. CanActivateChild- to control navigation to a child route. Refer Angular CanActivateChild Guard to protect Child Routes to know more about CanActivateChild.
  3. CanDeactivate- to control navigation away from the current route by asking user if it is ok to navigate away. Refer CanDeactivate Guard in Angular With Example to know more about CanDeactivate.
  4. Resolve- to perform route data retrieval before route activation.
  5. CanLoad- to mediate navigation to a feature module loaded asynchronously.

CanActivate interface in Angular

A class implementing CanActivate interface can act as a guard deciding if a route can be activated. If all guards return true, navigation continues. If any guard returns false, navigation is cancelled. If any guard returns a UrlTree, the current navigation is cancelled and a new navigation begins to the UrlTree returned from the guard.

There is one method in CanActivate interface

canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree

A routing guard can return an Observable<boolean> or a Promise<boolean> asynchronously or a boolean synchronously.

How to use CanActivate routeguard

First thing you need to do is to create a class that implements CanActivate interface. Add that guard to the route which has to be guarded. For example there is a class AuthGuard that implements CanActivate and using that you want to guard the route for account and its child routes.

{path: 'account', canActivate: [AuthGuard], component: AccountsComponent, children: [
 {path: ':acctno', component: AccountComponent}
 ]},

As you can see canActivate property is used for adding guards which takes an array of guards that has to be applied on a specific route.

Authentication using CanActivate routeguard Angular example

In the example we want to ensure that AccountComponent can only be accessed when the user is logged in.

First we’ll create a simple AuthenticationService responsible for authentication and authorization of routes. For simplicity here we’ll check for hardcoded values ‘admin’ and ‘angular’ as the correct values for user and password rather than fetching the values from persistence.

If value matches, username is also stored into localStorage for further use.

AuthService (auth.service.ts)

import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class AuthService{
  login(user: string, password: string): boolean{
    if(user==='admin' && password==='angular'){
      localStorage.setItem('user', user);
      return true;
    }
    return false;   
  }
  logout(){
    localStorage.removeItem('user');
  }

  getUser(): string{
    return localStorage.getItem('user');
  }

  isLoggedIn(): boolean{
    return localStorage.getItem('user') !== null;
  }
}

AuthGuard (authguard.service.ts)

This is the guard class that implements CanActivate interface. In the canActivate() method you check if the user is already logged in or not, if yes then return true otherwise navigate to home page.

import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree, Router } from '@angular/router';
import { Injectable } from '@angular/core';
import { AuthService } from './services/auth.service';
import { Observable } from 'rxjs';

@Injectable()
export class AuthGuard implements CanActivate{
  constructor(private authService: AuthService, private router: Router){}

  canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
    const isLoggedIn = this.authService.isLoggedIn();
    if(isLoggedIn){
      return true;
    }else{
      this.router.navigate(['/home']);
    }
  }
}

LoginComponent (login.component.ts)

In the component there is a login() method that calls the login method of the AuthService by passing the entered values of user and password fields. If there is no match then the error message is set otherwise navigate to home page.

import { Component } from '@angular/core';
import { AuthService } from './services/auth.service';
import { Router } from '@angular/router';

@Component({
  selector: 'app-login',
  templateUrl: 'login.component.html'
})
export class LoginComponent{
  message: string;
  loggedIn = false;
  constructor(private authService: AuthService, private router: Router){
    this.message = '';
    this.loggedIn = this.authService.isLoggedIn();
  }

  login(user: string, password: string){
    if(!this.authService.login(user, password)){
      this.message = 'User Name or Password is incorrect';
      return false;
    }else{
      this.router.navigate(['/home']);
    }
  }
}

login.component.html

<form *ngIf="!loggedIn">
  <div class="alert alert-danger" role="alert" *ngIf="message">
    {{ message }}
  </div>
  <div class="form-group">
    <label for="username">User:</label>
    <input class="form-control" name="username" placeholder="Enter user name" #username>
  </div>

  <div class="form-group">
    <label for="password">Password:</label>
    <input class="form-control" type="password" name="password" placeholder="Enter password" #password>
  </div>

  <button type="submit" class="btn btn-primary" (click)="login(username.value, password.value)">
    Login
  </button>
</form>

HomeComponent (home.component.ts)

This is the component for showing home page where you have a login button.

import { OnInit, Component } from '@angular/core';
import { Router } from '@angular/router';
import { AuthService } from './services/auth.service';

@Component({
    selector: 'app-home',
    templateUrl: './home.component.html'
})
export class HomeComponent implements OnInit {
  loggedIn = false;
  user: string;
  constructor(private authService: AuthService, private router: Router) { 
    this.loggedIn = this.authService.isLoggedIn();
    this.user = this.authService.getUser();
  }
  
  ngOnInit() {
  }
  onClickLogin(){
    this.router.navigate(['/login'])
  }
    
  logout(){
    this.authService.logout();
  }
}

home.component.html

<h4>Welcome to XYZ Bank</h4>
<div *ngIf="!loggedIn">
  <button class="btn btn-primary" (click)="onClickLogin()">Login</button>
</div>
<div *ngIf="loggedIn">
  Logged in as <b>{{ user }}</b>
  <a href (click)="logout()"> Log out</a>
</div>

AccountsComponent (accounts.component.ts)

This is the parent component which shows the account numbers.

import { Component, OnInit } from '@angular/core';
import { Router, ActivatedRoute } from '@angular/router';

@Component({
  selector: 'app-accounts',
  templateUrl: './accounts.component.html'
})
export class AccountsComponent implements OnInit{
  accountnumbers = [];
  constructor(private router: Router, private route: ActivatedRoute) {}
  ngOnInit() {
    this.accountnumbers = ['A1001', 'A1002'];
  }
}

accounts.component.html

<div class= "row">
  <div class="col-xs-4 col-md-4">
    <h2>Account Numbers</h2>
    <div class="list-group">
      <a [routerLink]="['/account', accountnumber]"    
       href="#"      
       class="list-group-item"   
       *ngFor="let accountnumber of accountnumbers">
          {{ accountnumber }}
      </a>
    </div>
  </div>
  <div class="col-xs-4 col-md-4">
    <router-outlet></router-outlet>
  </div>
</div>

AccountComponent (account.component.ts)

This is the child component that shows the details for the selected account number.

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Params, Router, RouterEvent } from '@angular/router';

@Component({
    selector: 'app-account',
    templateUrl: './account.component.html'
})
export class AccountComponent implements OnInit{
  acctNo: string;
  account: {accountnumber: string, type: string, balance: number};
  constructor(private route: ActivatedRoute, private router: Router){ }
  accountDetails = [
    {
      accountnumber: 'A1001',
      type: 'Saving', 
      balance: 22000
    },
    {
      accountnumber: 'A1002',
      type: 'Checking',
      balance: 1000
    }
  ];

  ngOnInit() {
    this.route.params.subscribe((params: Params)=> {
      this.acctNo = params['acctno'];
      this.account = this.accountDetails.find(e=>e.accountnumber === this.acctNo);
    });
  }
}

account.component.html

<h2>Account Details</h2>
<div class="row">
  <div class="col-xs-6">
    <label>Account Number: </label> {{ account.accountnumber }}
  </div>
</div>
<div class="row">
  <div class="col-xs-6">
    <label>Account Type: </label> {{ account.type }}
  </div>
</div>
<div class="row">
  <div class="col-xs-6">
    <label>Balance: </label> {{account.balance}}
  </div>
</div>

app-routing.module.ts

This is the routing module where we have the route definitions. With AccountsComponent route there is a canActivate guard class specified. This route guard will work on the child route AccountComponent too.

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { AccountsComponent } from './accounts/accounts.component';
import { AccountComponent } from './accounts/account/account.component';
import { HomeComponent } from './home.component';
import { AuthGuard } from './authguard.service';
import { LoginComponent } from './login.component';

const routes: Routes = [
                {path: 'home', component: HomeComponent},  
                {path: 'login', component: LoginComponent},                
                {path: 'account', canActivate: [AuthGuard], component: AccountsComponent, children: [
                     {path: ':acctno', component: AccountComponent}
                ]},                        
                {path: '', redirectTo:'/home', pathMatch: 'full'}             
];

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

app.component.html

This is the root template where menu options with route links are specified.

<nav class="navbar navbar-expand-md bg-dark navbar-dark">
  <div class="container-fluid">
    <div class="collapse navbar-collapse" id="collapsibleNavbar">
      <ul class="nav navbar-nav">
        <li class="nav-item" routerLinkActive="active">
          <a class="nav-link" routerLink="/home">Home</a>
        </li>
        <li class="nav-item" routerLinkActive="active">
          <a class="nav-link" routerLink="/account">Accounts</a>
        </li>
      </ul>
    </div>
  </div>
</nav>
<div class="container">
  <div class="row"><p></p></div>
  <div class="row">
    <div class="col-md-12">
      <router-outlet></router-outlet>
    </div>
  </div>
</div>

All the components (in decarations) and services (in providers) are to be added to the app module too.

providers: [AuthService, AuthGuard],

imports: [
  BrowserModule,
  AppRoutingModule
],

Once the configuration is done and code is successfully compiled access localhost:4200

route guard in Angular

If you try to access Account option without logging in you won’t be able to access it because of the canActivate() method implementation.

Login Page

Angular CanActivate example
canActivate route guard

Reference: https://angular.io/guide/router-tutorial-toh#milestone-5-route-guards

That's all for this topic Angular Access Control CanActivate Route Guard. If you have any doubt or any suggestions to make please drop a comment. Thanks!

>>>Return to Angular Tutorial Page


Related Topics

  1. Angular Route Parameters - Setting and Fetching
  2. Passing Query Parameters in Angular Routing
  3. Setting Wild Card Route in Angular
  4. Nested Route (Child Route) in Angular
  5. Highlight Currently Selected Menu Item Angular Routing Example

You may also like-

  1. Angular Project Structure With File Description
  2. Service in Angular With Examples
  3. Angular Custom Event Binding Using @Output Decorator
  4. Angular Class Binding With Examples
  5. Lambda Expression Examples in Java
  6. Object class in Java
  7. Bubble Sort Program in Python
  8. Spring Object XML Mapping (OXM) JAXB Example

No comments:

Post a Comment