Open In App

Angular Authentication Using Route Guards

In Angular, implementing authentication is important for securing routes and controlling access to different parts of the application based on user authentication status. Angular provides a powerful feature called Route Guards, and among them, the Auth Guard is used to control navigation based on the user's authentication state. In this article, we'll explore how to implement authentication using Auth Guards in Angular applications.

Prerequisites:

Authentication Guards

Auth Guard is a type of Route Guard in Angular that controls navigation to a route based on whether the user is authenticated or not. It intercepts navigation requests and either allows or denies access to certain routes based on predefined conditions. Auth Guards are commonly used where certain routes should only be accessible to authenticated users.

Authentication guards in Angular can be implemented using the CanActivate Interface.

CanActivate Interface

It helps in determining whether a route can be activated or not. Basically it works as gate keeper for the routes.

Syntax:

canActivate(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): boolean {
return this.checkAuthenticationStatus();
}

Features of Auth Guards

  1. Route Protection: Auth Guard provides a mechanism to protect routes in Angular applications, ensuring that only authenticated users can access certain parts of the application.
  2. Dynamic Routing: Auth Guards support dynamic routing based on the user's authentication status. Depending on whether the user is authenticated or not, the Auth Guard can redirect the user to different routes.
  3. Integration with Authentication Services: Auth Guards easily integrate with authentication services in Angular applications. They rely on authentication services to determine the authentication status of the user.
  4. Flexibility: Auth Guards offer flexibility in defining authentication rules and conditions for route access. You can customize Auth Guards to implement various authentication strategies, such as role-based access control (RBAC), token-based authentication, or custom authentication logic.
  5. Error Handling and Redirects: Auth Guards handle error scenarios and redirects accordingly. In cases where the user is not authenticated and tries to access a protected route, the Auth Guard can redirect the user to a login page or display a customized error message.

Steps to create the application

Step 1: Create the main folder for the project

mkdir auth-guard-application

Step 2: Initialize the node.js project

npm init -y

Step 3: Install the required dependencies

npm install express mongoose cors body-parser jsonwebtoken bcryptjs nodemon

The updated dependencies in package.json file of backend will look like:

"dependencies": {
"bcryptjs": "^2.4.3",
"body-parser": "^1.20.2",
"cors": "^2.8.5",
"express": "^4.19.2",
"jsonwebtoken": "^9.0.2",
"mongoose": "^8.3.0",
"nodemon": "^3.1.0"
}

Project Structure (Backend):

Screenshot-2024-04-17-000816

PROJECT STRUCTURE FOR BACKEND

Example: Create the required files as seen on the folder structure and add the following codes.

// authController.js

const User = require("../model/User");
const bcrypt = require("bcryptjs");
const jwt = require("jsonwebtoken");

exports.register = async (req, res) => {
    try {
        const { username, email, password } = req.body;
        let user = await User.findOne({ email });
        if (user) {
            return res.status(400).json({
                message: "User Already Exist",
                success: false,
            });
        }
        user = new User({
            username: username,
            email: email,
            password: password,
        });
        const salt = await bcrypt.genSalt(10);
        user.password = await bcrypt.hash(password, salt);
        await user.save();
        const token = generateJwtToken(user.id);
        res.status(201).json({
            success: true,
            token: token,
            message: "User registered successfully",
        });
    } catch (error) {
        res.status(500).json({
            message: "Server error! New user registration failed",
            success: false,
        });
    }
};

exports.login = async (req, res) => {
    try {
        const { email, password } = req.body;
        const user = await User.findOne({ email });
        if (!user) {
            return res.status(400).json({
                message: "Invalid credentials",
                success: false,
            });
        }
        const isMatched = await bcrypt.compare(password, user.password);
        if (!isMatched) {
            return res.status(400).json({
                message: "Invalid credentials",
                success: false,
            });
        }
        const token = generateJwtToken(user.id);
        res.status(200).json({
            success: true,
            message: "User logged in successfully",
            token: token,
        });
    } catch (error) {
        return res.status(500).json({
            success: false,
            message: "Internal Server Error, Login unsuccessful",
        });
    }
};

function generateJwtToken(userID) {
    const payload = {
        user: {
            id: userID,
        },
    };
    return jwt.sign(payload, "jwtSecret", { expiresIn: 3600 });
}

exports.getUserDetailsFronUserId = async (req, res) => {
    try {
        const { id } = req.params;
        const user = await User.findById(id);
        return res.status(200).json(user);
    } catch (error) {
        res.status(500).json({
            success: false,
            message: error.message,
        });
    }
};
// User.js - authModel

const mongoose = require('mongoose');

const userSchema = new mongoose.Schema({
    username: {
        type: String,
        required: true
    },
    email: {
        type: String,
        required: true,
        uniques: true
    },
    password: {
        type: String,
        required: true
    }
});

module.exports = mongoose.model('User', userSchema);
// authRoutes.js

const express = require('express');
const router = express.Router();
const authController = require('../controller/authController');

router.post('/register', authController.register);

router.post('/login', authController.login);

router.get('/:id', authController.getUserDetailsFronUserId);

module.exports = router;
// server.js

const express = require("express");
const cors = require("cors");
const mongoose = require("mongoose");
const authRoutes = require("../backend/route/authRoutes");

const app = express();
app.use(cors());
app.use(express.json());

mongoose
    .connect("mongodb://localhost:27017/auth-guard", {
        family: 4,
    })
    .then(() => console.log("Mongo DB connected"))
    .catch((err) => console.log(err));

app.use("/api/auth", authRoutes);

const PORT = 5000;
app.listen(PORT, () => console.log(`Server started on port ${PORT}`));

To start the backend run the following command

nodemon server.js

Steps to Create a Angular App and Installing Module

Step 1: Install the angular CLI

npm install -g @angular/cli

Step 2: Create a new angular project

ng new frontend

Step 3: Create components for different functionalities in angular

Syntax - ng generate component <component-name>
ng generate component admin
ng generate component home
ng generate component sidebar
ng generate component user

Step 4: Create services for communication between frontend and backend

Syntax - ng generate service <service-name>
ng generate service auth-guard
ng generate service shared
ng generate service user

Project Structure (Frontend):

frontend-structure

PROJECT STRUCTURE FRONTEND

Example: Create the required files as seen on the folder structure and add the following codes.

<!-- user.component.html -->

<div class="error-message" *ngIf="errorMessage">{{ errorMessage }}</div>
<div class="success-message" *ngIf="successMessage">{{ successMessage }}</div>
<div class="container" *ngIf="loginActive">
  <h2>Login</h2>
  <form (ngSubmit)="login()">
    <div class="form-group">
      <label for="email">Email:</label>
      <input
        type="email"
        class="form-control"
        id="email"
        name="email"
        [(ngModel)]="email"
        required
      />
    </div>
    <div class="form-group">
      <label for="password">Password:</label>
      <input
        type="password"
        class="form-control"
        id="password"
        name="password"
        [(ngModel)]="password"
        required
      />
    </div>
    <button type="submit" class="btn btn-primary" href="">Login</button>
  </form>
</div>
<div class="container" *ngIf="registerActive">
  <h2>Register</h2>
  <form (submit)="register()">
    <div class="form-group">
      <label for="username">Username</label>
      <input
        type="text"
        id="username"
        class="form-control"
        [(ngModel)]="username"
        name="username"
        required
      />
    </div>
    <div class="form-group">
      <label for="email">Email</label>
      <input
        type="email"
        id="email"
        class="form-control"
        [(ngModel)]="email"
        name="email"
        required
      />
    </div>
    <div class="form-group">
      <label for="password">Password</label>
      <input
        type="password"
        id="password"
        class="form-control"
        [(ngModel)]="password"
        name="password"
        required
      />
    </div>
    <button type="submit" class="btn btn-primary">Register</button>
  </form>
</div>
<!-- admin.component.html -->

<h1>Welcome to the Admin Page!</h1>
<p>This is another protected route accessible only to authenticated users.</p>
<!-- home.component.html -->

<h1>Welcome to the Home Page!</h1>
<p>This is an unprotected route accessible to all the users.</p>
<!-- sidebar.component.html -->

<ul class="nav">
    <li><a (click)="loadContent('home')">Home</a></li>
    <li><a (click)="loadContent('admin')">Admin</a></li>
</ul>
<!-- app.component.html -->

<nav class="navbar">
    <div class="navbar-title">{{ title }}</div>
    <ul class="navbar-menu">
        <li><a href="#" (click)="login()" *ngIf="!isLoggedIn">Login</a></li>
        <li><a href="#" (click)="register()" *ngIf="!isLoggedIn">Register</a></li>
        <li><a href="#" (click)="logout()" *ngIf="isLoggedIn">Logout</a></li>
    </ul>
</nav>

<div class="container">
    <app-sidebar (contentLoad)="loadContent($event)"></app-sidebar>
    <div class="content">
        <router-outlet></router-outlet>
    </div>
</div>
/* user.component.css */

.container {
    width: 50%;
    margin: 2rem auto;
    padding: 1.5vmax;
    padding-right: 2.5vmax;
    border: 1px solid #ccc;
    border-radius: 5px;
}

h2 {
    text-align: center;
    margin-bottom: 20px;
    font-size: 2rem;
}

.form-group {
    margin-bottom: 20px;
}

label {
    display: block;
    margin-bottom: 5px;
}

input[type="text"],
input[type="email"],
input[type="password"] {
    width: 97%;
    padding: 10px;
    border: 1px solid #ccc;
    border-radius: 5px;
}

button[type="submit"] {
    width: 20%;
    padding: 1.1vmax;
    background-color: #0056b3;
    color: #fff;
    border: none;
    border-radius: 5px;
    cursor: pointer;
    font-weight: bold;
    font-size: 1rem;
    align-self: center;
    margin-top: 1vmax;
}

.container {
    width: 50%;
    margin: 2rem auto;
    padding: 1.5vmax;
    padding-right: 3.5vmax;
    border: 1px solid #ccc;
    border-radius: 5px;
}

h2 {
    text-align: center;
    margin-bottom: 20px;
    font-size: 2rem;
}

.form-group {
    margin-bottom: 20px;
}

label {
    display: block;
    margin-bottom: 5px;
}

input[type="email"],
input[type="password"] {
    width: 99%;
    padding: 10px;
    border: 1px solid #ccc;
    border-radius: 5px;
}

button[type="submit"] {
    width: 20%;
    padding: 1.1vmax;
    background-color: #0056b3;
    color: #fff;
    border: none;
    border-radius: 5px;
    cursor: pointer;
    font-weight: bold;
    font-size: 1rem;
    align-self: center;
    margin-top: 1vmax;
}

.error-message {
    color: #FF0000;
    background-color: #FFEFEF;
    padding: 10px;
    border: 1px solid #FF0000;
    border-radius: 5px;
    margin-bottom: 10px;
}

.success-message {
    color: green;
    background-color: rgb(185, 231, 185);
    padding: 10px;
    border: 1px solid green;
    border-radius: 5px;
    margin-bottom: 10px;
}
/* sidebar.component.css */

ul {
    border: 1px solid lightgray;
    margin-top: 0%;
    border-radius: 10px;
}

li {
    list-style-type: none;
    margin: 3vmax;
    cursor: pointer;
    box-sizing: border-box;
}

li:hover {
    color: lightgray;
}

a {
    color: lightgray;
    text-decoration: none;
}

a:hover {
    color: white;
    text-decoration: none;
}
/* app.component.css */

.navbar {
    background-color: #333;
    color: #fff;
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 1.5vmax 3vmax;
}

.navbar-title {
    font-size: 1.7rem;
}

.navbar-menu {
    list-style-type: none;
    padding: 0;
    margin: 0;
}

.navbar-menu li {
    display: inline;
    margin-right: 2vmax;
    font-size: 1.3rem;
}

.navbar-menu li:last-child {
    margin-right: 0;
}

.navbar-menu li a {
    color: #fff;
    text-decoration: none;
}

.navbar-menu li a:hover {
    text-decoration: underline;
}

.container {
    display: flex;
    height: 100%;
    min-height: 87vh;
    margin-top: 0.4vmax;
}

.content {
    flex: 1;
    padding: 20px;
}

app-sidebar {
    width: 25%;
    background-color: #333;
    padding: 20px;
    color: white;
    font-size: 1.4rem;
}
// user.component.ts

import { Component } from "@angular/core";
import { UserService } from "../user.service";
import { Router } from "@angular/router";
import { SharedService } from "../shared.service";
import { FormsModule } from "@angular/forms";
import { CommonModule } from "@angular/common";

@Component({
    selector: "app-user",
    standalone: true,
    imports: [FormsModule, CommonModule],
    templateUrl: "./user.component.html",
    styleUrl: "./user.component.css",
})
export class UserComponent {
    username!: string;
    email!: string;
    password!: string;
    credentials: any = {};
    successMessage: string = "";
    errorMessage: string = "";
    loginActive: boolean = true;
    registerActive: boolean = false;
    constructor(
        private authService: UserService,
        private router: Router,
        private sharedService: SharedService
    ) { }
    ngOnInit(): void {
        this.sharedService.loginEvent.subscribe(() => {
            this.errorMessage = "";
            this.successMessage = "";
            this.loginActive = true;
            this.registerActive = false;
        });
        this.sharedService.registerEvent.subscribe(() => {
            this.errorMessage = "";
            this.successMessage = "";
            this.registerActive = true;
            this.loginActive = false;
        });
    }

    login(): void {
        const credentials = {
            email: this.email,
            password: this.password,
        };
        this.authService.login(credentials).subscribe(
            (response: any) => {
                const token = response.token;
                localStorage.setItem("token", token);
                this.authService.emitLoggedInEvent();
                this.loginActive = false;
                this.registerActive = false;
                this.email = "";
                this.password = "";
                this.successMessage = response.message;
            },
            (error: any) => {
                console.error("Error logging in:", error);
                this.errorMessage =
                    "Login unsuccessfull ! Please reload or try in incognito tab";
            }
        );
        this.errorMessage = "";
        this.successMessage = "";
        this.email = "";
        this.password = "";
    }

    register(): void {
        const userData = {
            username: this.username,
            email: this.email,
            password: this.password,
        };

        this.authService.register(userData).subscribe(
            (response: any) => {
                this.successMessage = response.message;
                this.loginActive = true;
                this.registerActive = false;
                this.username = "";
                this.email = "";
                this.password = "";
            },
            (error: any) => {
                console.error(error);
                this.errorMessage = "User not registered successfully";
            }
        );
        this.username = "";
        this.email = "";
        this.password = "";
    }
}
// admin.component.ts

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

@Component({
    selector: 'app-admin',
    standalone: true,
    imports: [],
    templateUrl: './admin.component.html',
    styleUrl: './admin.component.css'
})
export class AdminComponent {

}
// sidebar.component.ts

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

@Component({
    selector: 'app-sidebar',
    standalone: true,
    imports: [],
    templateUrl: './sidebar.component.html',
    styleUrl: './sidebar.component.css'
})
export class SidebarComponent {
    isLoggedIn: boolean = true;
    constructor() { }
    ngOnInit(): void {
        if (typeof localStorage !== 'undefined') {
            const token = localStorage.getItem('token');
            if (token) {
                this.isLoggedIn = true;
            }
        }
    }
    @Output() contentLoad = new EventEmitter<string>();

    loadContent(page: any) {
        console.log("Event emit");
        this.contentLoad.emit(page);
    }
}
// home.compoennt.ts

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

@Component({
    selector: 'app-home',
    standalone: true,
    imports: [],
    templateUrl: './home.component.html',
    styleUrl: './home.component.css'
})
export class HomeComponent {

}
// app.component.ts

import { Component } from "@angular/core";
import { Router, RouterOutlet } from "@angular/router";
import { UserService } from "./component/user.service";
import { SharedService } from "./component/shared.service";
import { FormsModule } from "@angular/forms";
import { CommonModule } from "@angular/common";
import { SidebarComponent } from "./component/sidebar/sidebar.component";

@Component({
    selector: "app-root",
    standalone: true,
    imports: [RouterOutlet, FormsModule, CommonModule, SidebarComponent],
    templateUrl: "./app.component.html",
    styleUrl: "./app.component.css",
})
export class AppComponent {
    title = "AuthGuard in Angular";
    isLoggedIn: boolean = false;
    constructor(
        private router: Router,
        private authService: UserService,
        private sharedService: SharedService
    ) { }
    ngOnInit(): void {
        this.authService.loggedInEvent.subscribe((data: any) => {
            this.isLoggedIn = true;
        });
        if (typeof localStorage !== "undefined" && localStorage.getItem("token")) {
            this.isLoggedIn = true;
        }
    }

    login(): void {
        this.sharedService.triggerLoginEvent();
        this.router.navigate(["/"]);
    }

    register(): void {
        this.sharedService.triggerRegisterEvent();
        this.router.navigate(["/"]);
    }

    logout(): void {
        this.isLoggedIn = false;
        localStorage.removeItem("token");
        this.router.navigate(["/"]);
    }

    loadContent(page: any) {
        if (page === "home") {
            this.router.navigate(["/home"]);
        } else if (page === "admin") {
            this.router.navigate(["/admin"]);
        }
    }
}
// app.routes.ts

import { Routes } from '@angular/router';
import { UserComponent } from './component/user/user.component';
import { HomeComponent } from './component/home/home.component';
import { AuthGuardService } from './component/auth-guard.service';
import { AdminComponent } from './component/admin/admin.component';

export const routes: Routes = [
    { path: '', component: UserComponent },
    { path: 'home', component: HomeComponent },
    { path: 'admin', component: AdminComponent, canActivate: [AuthGuardService] },
];
// app.module.ts

import { InjectionToken, NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { AppComponent } from './app.component';
import { JwtHelperService, JWT_OPTIONS } from '@auth0/angular-jwt';
import { RouterModule } from '@angular/router';
import { routes } from './app.routes';
import { UserComponent } from './component/user/user.component';
import { HomeComponent } from './component/home/home.component';
import { AdminComponent } from './component/admin/admin.component';
@NgModule({
    declarations: [
        AppComponent,
        UserComponent,
        HomeComponent,
        AdminComponent
    ],
    imports: [
        BrowserModule,
        FormsModule,
        RouterModule.forRoot(routes),
    ],
    exports: [RouterModule],
    providers: [{ provide: JWT_OPTIONS, useValue: JWT_OPTIONS }, JwtHelperService],
    bootstrap: [AppComponent]
})
export class AppModule { }
// user.service.ts

import { HttpClient } from "@angular/common/http";
import { EventEmitter, Injectable } from "@angular/core";
import { Observable, tap } from "rxjs";

@Injectable({
    providedIn: "root",
})
export class UserService {
    private baseUrl = "http://localhost:5000/api/auth";
    private readonly TOKEN_KEY = "token";
    constructor(private httpClient: HttpClient) { }

    register(userData: any): Observable<any> {
        return this.httpClient.post(`${this.baseUrl}/register`, userData);
    }

    login(credentials: any): Observable<any> {
        return this.httpClient.post(`${this.baseUrl}/login`, credentials).pipe(
            tap((response: any) => {
                if (response && response.token) {
                    localStorage.setItem(this.TOKEN_KEY, response.token);
                    this.emitLoggedInEvent();
                }
            })
        );
    }

    loggedInEvent: EventEmitter<any> = new EventEmitter();
    emitLoggedInEvent() {
        this.loggedInEvent.emit();
    }

    getToken(): string | null {
        if (typeof window !== "undefined") {
            return localStorage.getItem(this.TOKEN_KEY);
        }
        return null;
    }

    isAuthenticatedUser(): boolean {
        return !!this.getToken();
    }
}
// shared.service.ts

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

@Injectable({
    providedIn: 'root'
})
export class SharedService {
    loginEvent: EventEmitter<void> = new EventEmitter<void>();
    registerEvent: EventEmitter<void> = new EventEmitter<void>();
    constructor() { }

    triggerLoginEvent(): void {
        this.loginEvent.emit();
    }

    triggerRegisterEvent(): void {
        this.registerEvent.emit();
    }
}
// auth-guard.service.ts

import { Injectable } from '@angular/core';
import { UserService } from './user.service';
import { Router } from '@angular/router';

@Injectable({
    providedIn: 'root'
})
export class AuthGuardService {
    constructor(private authService: UserService, private router: Router) { }

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

    private checkLoggedIn(): boolean {
        if (this.authService.isAuthenticatedUser()) {
            return true;
        } else {
            this.router.navigate(['/']);
            return false;
        }
    }
}

Output:

Route Guards Angular Examples

OUTPUT GIF

Article Tags :