Introduction

In this article, we are going to discuss the basics of auth guards and step-by-step implementation with the help of different use cases.

Agenda

Prerequisites

What is Angular?

Angular is a popular open-source JavaScript framework for building web applications. It was developed by Google and is currently maintained by the Angular Team at Google. Angular allows developers to create dynamic, single-page applications (SPAs) and provides a structured approach to building complex web applications.

Auth Guards in Angular

Types of Auth Guards in Angular

In Angular, there are different types of Auth Guards that you can use to protect routes based on the user’s authentication status.

1. CanActivate

2. CanActivateChild

3. CanDeactivate

4. CanLoad

Benefits of Auth Guards

Auth Guards in Angular provide the following important benefits for building secure web applications.

Example of Auth Guards

Step 1. Create a new Angular application.

ng new angular-guards

Step 2. Install the bootstrap module with the help of the following command.

npm install bootstrap

configure bootstrap in an Angular JSON file.

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

Step 3. Add the following components to the newly created application.

ng generate component components/login
ng generate component components/product-list
ng generate component components/product-details
ng generate component components/product-offers
ng generate component components/product-ratings

login.component.html

<div class="container">
    <div class="row justify-content-center mt-5">
      <div class="col-md-6">
        <form [formGroup]="loginForm" (ngSubmit)="onSubmit()">
          <div class="form-group">
            <label for="username">Username</label>
            <input type="text" class="form-control" id="username" formControlName="username" placeholder="Enter username">
          </div>
          <div class="form-group">
            <label for="password">Password</label>
            <input type="password" class="form-control" id="password" formControlName="password" placeholder="Password">
          </div>
          <button type="submit" class="btn btn-primary" style="margin-top: 6px;">Login</button>
        </form>
      </div>
    </div>
  </div>

login.component.ts

import { Component } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { Router } from '@angular/router';
import { AuthService } from 'src/app/services/auth.service';

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

  constructor(private fb: FormBuilder, private authService : AuthService, private router: Router) {}

  ngOnInit(): void {
    this.loginForm = this.fb.group({
      username: ['', Validators.required],
      password: ['', Validators.required]
    });
  }

  onSubmit(): void {
    if (this.loginForm.valid) {
      const username = this.loginForm.get('username').value;
      const password = this.loginForm.get('password').value;

       // Call the authentication service's login method
       if (this.authService.login(username, password)) {
        // Navigate to the ProductListComponent upon successful login
        this.router.navigate(['/product-list']);
      } else {
        // Handle authentication error (show error message, etc.)
      }

    }
  }
}

product-list.component.html

<table class="table table-bordered border-primary">
    <thead *ngIf="productData != null">
      <tr>
        <th scope="col">Id</th>
        <th scope="col">Title</th>
        <th scope="col">Description</th>
        <th scope="col">Price</th>
        <th scope="col">Category</th>
      </tr>
    </thead>
    <tbody>
      <tr *ngFor="let product of productData">
        <th scope="row"><a [routerLink]="['/product', product.id]">{{product.id}}</a></th>
        <td>{{product.title}}</td>
        <td>{{product.description}}</td>
        <td>{{product.price | currency}}</td>
        <td>{{product.category}}</td>
      </tr>
    </tbody>
  </table>

product-list.component.ts

import { Component } from '@angular/core';
import { ProductService } from 'src/app/services/product.service';

@Component({
  selector: 'app-product-list',
  templateUrl: './product-list.component.html',
  styleUrls: ['./product-list.component.css']
})
export class ProductListComponent {
  constructor(private productService : ProductService){}

  productData : any;

  ngOnInit(): void {
    this.productService.getProducts().subscribe(
      (data: any[]) => {
        this.productData = data;
      },
      (error) => {
        console.error('Error fetching products:', error);
      }
    );
  }
}

proudct-details.component.html

<table class="table table-bordered border-primary">
    <thead *ngIf="productDetail != null">
        <tr>
            <th scope="col">Id</th>
            <th scope="col">Title</th>
            <th scope="col">Description</th>
            <th scope="col">Price</th>
            <th scope="col">Category</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <th scope="row">{{productDetail?.id}}</th>
            <td>{{productDetail?.title}}</td>
            <td>{{productDetail?.description}}</td>
            <td>{{productDetail?.price | currency}}</td>
            <td>{{productDetail?.category}}</td>
        </tr>
    </tbody>
</table>

<div class="btn-group" role="group" aria-label="Basic mixed styles example">
    <button type="button" class="btn btn-danger"><a class="nav-link" routerLink="offers">Product Offers</a></button>
    <button type="button" class="btn btn-warning"><a class="nav-link" routerLink="rating">Product Rating</a></button>
</div>

<router-outlet></router-outlet>

proudct-details.component.ts

import { Component } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { ProductService } from 'src/app/services/product.service';

@Component({
  selector: 'app-proudct-details',
  templateUrl: './proudct-details.component.html',
  styleUrls: ['./proudct-details.component.css']
})
export class ProudctDetailsComponent {
  productDetail? : any;

  constructor(private route : ActivatedRoute,private productService : ProductService){}

  ngOnInit(): void {
    let productId = this.route.snapshot.params['id'];
    this.getProductDetailById(productId)
  }

  getProductDetailById(id: number){
    this.productService.getProductDetailById(id).subscribe(res => {
      this.productDetail = res
      console.log(res)
    })
  }
}

proudct-offers.component.html

<p>proudct-offers works!</p>

proudct-offers.component.ts

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

@Component({
  selector: 'app-proudct-offers',
  templateUrl: './proudct-offers.component.html',
  styleUrls: ['./proudct-offers.component.css']
})
export class ProudctOffersComponent {

}

proudct-rating.component.html

<div class="product-rating container">
  <form (ngSubmit)="rateProduct()" #ratingForm="ngForm" class="mb-3" style="margin-top: 2%;">
    <div class="form-group">
      <label for="rating">Rating:</label>
      <input type="number" id="rating" name="rating" min="1" max="5" required
             [(ngModel)]="selectedRating" class="form-control">
    </div>
    
    <button type="submit" [disabled]="ratingForm.invalid" class="btn btn-primary" style="margin-top: 2%;">Rate Product</button>
  </form>
  <button (click)="saveChanges()" [disabled]="!hasUnsavedChanges()" class="btn btn-success">Save Changes</button>
</div>

proudct-rating.component.ts

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

@Component({
  selector: 'app-proudct-rating',
  templateUrl: './proudct-rating.component.html',
  styleUrls: ['./proudct-rating.component.css']
})
export class ProudctRatingComponent {
  
  private unsavedChanges = false;
  selectedRating: number | null = null;

  rateProduct() {
    if (this.selectedRating !== null) {
      console.log('Rating:', this.selectedRating);
      this.unsavedChanges = true;
    }
  }

  saveChanges() {
    console.log('Saving changes...');
    this.unsavedChanges = false;
  }

  hasUnsavedChanges(): boolean {
    return this.unsavedChanges;
  }
}

Step 4. Create an auth and product service with the help of the following command.

ng generate service services/product
ng generate service services/auth

product.service.ts

import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { AuthService } from './auth.service';
import { Observable } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class ProductService {

  private apiUrl = 'https://fakestoreapi.com';
  private authSecretKey = 'Bearer Token';

  constructor(private http: HttpClient) {}

  private getHeaders(): HttpHeaders {
    const authToken = localStorage.getItem(this.authSecretKey);
    return new HttpHeaders({
      'Content-Type': 'application/json',
      Authorization: `Bearer ${authToken}`
    });
  }

  getProducts() : Observable<any[]>{
    const headers = this.getHeaders();
    return this.http.get<any[]>(`${this.apiUrl}/products`, { headers });
  }

  getProductDetailById(id : number){
    const headers = this.getHeaders();
    return this.http.get(`${this.apiUrl}/products/` + id, { headers })
  }
}

auth.service.ts

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

@Injectable({
  providedIn: 'root'
})
export class AuthService {

  private isAuthenticated = false;
  private authSecretKey = 'Bearer Token';

  constructor() { 
    this.isAuthenticated = !!localStorage.getItem(this.authSecretKey);
  }
  
  login(username: string, password: string): boolean {
    if (username === 'Jaydeep Patil' && password === 'Pass@123') {
      const authToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpheWRlZXAgUGF0aWwiLCJpYXQiOjE1MTYyMzkwMjJ9.yt3EOXf60R62Mef2oFpbFh2ihkP5qZ4fM8bjVnF8YhA'; // Generate or receive the token from your server
      localStorage.setItem(this.authSecretKey, authToken);
      this.isAuthenticated = true;
      return true;
    } else {
      return false;
    }
  }

  isAuthenticatedUser(): boolean {
    return this.isAuthenticated;
  }

  logout(): void {
    localStorage.removeItem(this.authSecretKey);
    this.isAuthenticated = false;
  }
}

Step 5. Next, add one feature module, as shown below, that we used with the CanLoad auth guard.

ng generate module modules/product-service

product-service-routing.module.ts

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { ProductServiceComponent } from './product-service-component/product-service-component.component';

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

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

product-service.module.ts

ng generate component modules/product-service/product-service-component

product-service-component.component.html

<p>product-service-component works!</p>

product-service-component.component.ts

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

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

  constructor(){
    console.log('service component')
  }

  ngOnInit(): void {
    console.log('service component')
  }
  
}

Step 6. Add the following code related to navbar tabs inside the app component for navigation purposes.

app.component.html

<nav class="navbar navbar-expand-lg navbar-light bg-light">
  <div class="container-fluid">
    <a class="navbar-brand" href="#">Auth Guards Demo</a>
    <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
      <span class="navbar-toggler-icon"></span>
    </button>
    <div class="collapse navbar-collapse" id="navbarSupportedContent">
      <ul class="navbar-nav me-auto mb-2 mb-lg-0">
        <li class="nav-item">
          <a class="nav-link active" aria-current="page" routerLink="/login">Login</a>
        </li>
        <li class="nav-item">
          <a class="nav-link" routerLink="/products">Products</a>
        </li>
        <li class="nav-item">
          <a class="nav-link" routerLink="/service">Products Service</a>
        </li>
        <li class="nav-item">
          <a class="nav-link" (click)="logout()">Logout</a>
        </li>
      </ul>
    </div>
  </div>
</nav>

<router-outlet></router-outlet>

app.component.ts

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

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'angular-guards';
  
  constructor(private authService: AuthService, private router : Router) {}

  logout(): void {
    this.authService.logout();
    this.router.navigate(['/login']);
  }
}

Step 7. Create a new auth guard with the help of the following command and add the code as shown below.

ng generate guard guards/auth

auth.guard.ts

import { Injectable } from '@angular/core';
import { CanActivate, CanActivateChild, CanDeactivate, CanLoad, Router } from '@angular/router';
import { AuthService } from '../services/auth.service';
import { ProudctRatingComponent } from '../components/proudct-rating/proudct-rating.component';

@Injectable({
  providedIn: 'root'
})
export class AuthGuard implements CanActivate, CanActivateChild,CanDeactivate<ProudctRatingComponent>, CanLoad  {
  constructor(private authService: AuthService, private router: Router) {}

  canActivate(): boolean {
    return this.checkAuth();
  }

  canActivateChild(): boolean {
    return this.checkAuth();
  }

  canDeactivate(component: ProudctRatingComponent): boolean {
    if (component.hasUnsavedChanges()) {
      return window.confirm('You have unsaved changes. Do you really want to leave?');
    }
    return true;
  }

  canLoad(): boolean {
    return this.checkAuth();
  }

  private checkAuth(): boolean {
    if (this.authService.isAuthenticatedUser()) {
      return true;
    } else {
      // Redirect to the login page if the user is not authenticated
      this.router.navigate(['/login']);
      return false;
    }
  }

}

Step 8. Next, open the app routing file and configure different routes with auth guards.

app-routing.module.ts

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { LoginComponent } from './components/login/login.component';
import { ProductListComponent } from './components/product-list/product-list.component';
import { AuthGuard } from './guards/auth.guard';
import { ProudctDetailsComponent } from './components/proudct-details/proudct-details.component';
import { ProudctRatingComponent } from './components/proudct-rating/proudct-rating.component';
import { ProudctOffersComponent } from './components/proudct-offers/proudct-offers.component';

const routes: Routes = [
  {path: '', component:ProductListComponent, pathMatch:'full', canActivate: [AuthGuard]},
  {path: 'login', component:LoginComponent},
  {path: 'products', component:ProductListComponent, canActivate: [AuthGuard]},
  {path: 'products', component:ProductListComponent, canActivate: [AuthGuard]},
  {
    path: 'product/:id', component: ProudctDetailsComponent,canActivateChild: [AuthGuard],
    children: [
      { path: 'rating', component: ProudctRatingComponent, canDeactivate: [AuthGuard] },
      { path: 'offers', component: ProudctOffersComponent }
    ]
  },
  {
    path: 'service',
    loadChildren: () => import('./modules/product-service/product-service.module').then(m => m.ProductServiceModule),
    canLoad: [AuthGuard]
  },
  {path: '**', component:ProductListComponent, pathMatch:'full', canActivate: [AuthGuard]}
];

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

Step 9. Configure auth guards and import application-related modules inside the root module, as shown below.

app.module.ts

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { LoginComponent } from './components/login/login.component';
import { ProductListComponent } from './components/product-list/product-list.component';

import { HttpClientModule } from '@angular/common/http';
import {ReactiveFormsModule} from '@angular/forms'
import { AuthGuard } from './guards/auth.guard';
import { ProudctDetailsComponent } from './components/proudct-details/proudct-details.component';
import { ProudctRatingComponent } from './components/proudct-rating/proudct-rating.component';
import { ProudctOffersComponent } from './components/proudct-offers/proudct-offers.component';

import {FormsModule} from '@angular/forms'
import { ProductServiceModule } from './modules/product-service/product-service.module';

@NgModule({
  declarations: [
    AppComponent,
    LoginComponent,
    ProductListComponent,
    ProudctDetailsComponent,
    ProudctRatingComponent,
    ProudctOffersComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    HttpClientModule,
    ReactiveFormsModule,
    FormsModule,
    ProductServiceModule
  ],
  providers: [AuthGuard],
  bootstrap: [AppComponent]
})
export class AppModule { }

Step 10. Run your application.

ng serve

Sample Screenshots

Auth Guard Demo

Product List

Product List

Offers

Rating

Rating

GitHub

Conclusion

In this article, we discussed the basics of auth guards, types of guards, and their benefits with the help of different examples.

Happy Coding!