Setting Up Angular Authentication Using JWT


In this article, we will be building an authentication system in Angular using Expressjs, MongoDB, and JSON web token(JWT) for authentication.
We will start by creating a simple REST API with Expressjs and MongoDB that will enable a user to signup and login with their details.

Learning prerequisites

  • Basic familiarity with Javascript.
  • Basic Knowledge of Angular.
  • Basic Knowledge of REST APIs.
  • MongoDB installed on the development machine
  • Postman installed on the development machine

Setting up the backend.

We will start by setting up the backend service for our application, After that, we use the backend services in our angular application.

  • Setup the Nodejs project and file structure
  • Install all the required dependencies for our application.
  • Create an express server and configure all dependencies.
  • Setup the MongoDB database.
  • Create a User model
  • Create the necessary routes
  • Create the auth middleware.

Setup Nodejs project and file structure

Let’s start by setting up a new project for our application. To set up a Nodejs application we will need a package.json file to handle all of our project dependencies. So to create that we need to run npm init -y on our terminal. But first let’s create a directory for our application.

cd desktop
mkdir angular-auth && cd angular-auth
mkdir client && mkdir server
cd server
npm init -y
code .

The code . command will open up the application in visual studio code.
After doing this let’s create a file structure for our application. We will be using the MVC pattern. Go ahead and set up a file structure like the one below:

From the structure above, it will be very easy to configure our application and also making it very scalable.

Install all the required dependencies for our application

We need to install some dependencies for our application. I will explain all the dependencies below:

  • Expressjs: A node.js framework that makes it easy to build web applications. We will use expressjs for our routing.
  • MongoDB: A document-oriented NoSQL database used for high volume data storage.
  • mongoose-unique-validator: This is a plugin that adds pre-save validation for unique fields within a Mongoose schema.
  • mongoose: An object modeling tool used to asynchronous query MongoDB.
  • Bcrypt : This is used for hashing. We need it to hash our user’s passwords before storing them into the database.
  • Jsonwebtoken : JSON Web Token(JWT) will be used for authentication and authorization. This package will help to set up protected routes that only logged in users can access.
  • Nodemon : This is a watcher that restarts our server automatically when we make changes in out application.
  • Morgan : Morgan is a logger tool used to log all requests made on the server.
  • body-parser: body-parser is what allows express to read the request body and then parse that into a JSON object that we can be understood.
  • Cors : This is a middleware that can be used to enable CORS with various options.

To install all this dependencies open up the terminal and run the following:

npm i express mongoose mongoose-unique-validator bcrypt jsonwebtoken nodemon morgan body-parser cors --save

This command will install all these dependencies and add them among our dependencies in the package.js file. Notice that it also creates a node_modules directory, this is where all our packages are being stored. Remember not to add this package while you are pushing your project to git. To avoid this we need to create a .gitignore file. To do this open your terminal and run this command:


touch .gitignore

This command will create a .gitignore file. Open the file and type node_modules in it as add the folder as a gitignore file.

Create an express server and configure all dependencies

Lets head over to the src/index.js file and type the following code:


const express = require("express");
const PORT = process.env.PORT || 4000;
const morgan = require("morgan");
const cors = require("cors");
const bodyParser = require("body-parser");
const app = express();
app.use(cors()); // configure cors
//configure body parser
app.use(bodyParser.urlencoded({
    extended: false
}));
app.use(bodyParser.json());
//configure body-parser ends here
app.use(morgan("dev")); // configire morgan
// define first route
app.get("/", (req, res) => {
    console.log("Hello MEAN Soldier...Ready For Battle??");
});
app.listen(PORT, () => {
    console.log(`App is running on ${PORT}`);
});

In the above code snippet, brought in the dependencies and then configured them respectively and the setup a simple express server that listens to a port. To run the server open up your terminal and type the command nodemon src/index . You should be seeing App is running on 4000 on your terminal. That means we’ve successfully set up our express server.

Setup Mongodb database.

Go to src/config/db.js file and type the code below:


const mongoose = require("mongoose");
module.exports = function (app) {
    mongoose.connect("mongodb://localhost:27017/angular-auth", {
        useUnifiedTopology: true,
        useNewUrlParser: true,
        useFindAndModify: false
    }).then(connrction => console.log("Application is connected to db")).catch(err => console.log(err))
    mongoose.Promise = global.Promise;
    process.on("SIGINT", cleanup);
    process.on("SIGTERM", cleanup);
    process.on("SIGHUP", cleanup);
    if (app) {
        app.set("mongoose", mongoose);
    }
};
function cleanup() {
    mongoose.connection.close(function () {
        process.exit(0);
    });
}

Here we bring in the mongoose ORM and use it to connect to MongoDB.We use the mongoose.connect method to connect to the database. We pass the database URL and some options to the method params. This returns a promise which we now log something to show that that connection was successful and use the catch block to catch all possible errors.
Now we need to bring in the mongoose connection into the src/index.js file. To do that add this to the src/index.js file:

//bring in db config
require("./config/db")(app);
//db config ends here

After doing this you will now see something logged on your console:

If you see this know that you have successfully connected to the database.

Create User model

Now that we have created a connection to our database ,lets create a user model for our application which basically contains the following fields:

  • Name 
  • Password
  • Phone number 
  • Email
  • Timestamp(This will be auto generated)

const mongoose = require("mongoose");
var uniqueValidator = require('mongoose-unique-validator');
const bcrypt = require("bcrypt");
const jwt = require("jsonwebtoken");
const Schema = mongoose.Schema;
const userSchema = new Schema({
    name: {
        type: String,
        required: true,
    },
    password: {
        type: String,
        required: true
    },
    phone_number: {
        type: String,
        required: true
    },
    email: {
        type: String,
        required: true,
        unique: true
    },
}, {
    timestamps: true,
});

userSchema.methods.hashPassword = async (password) => {
    return await bcrypt.hashSync(password, 10);
}
userSchema.methods.compareUserPassword = async (inputtedPassword, hashedPassword) => {
    return await bcrypt.compare(inputtedPassword, hashedPassword)
}
userSchema.methods.generateJwtToken = async (payload, secret, expires) => {
    return jwt.sign(payload, secret, expires)
}
module.exports = mongoose.model("User", userSchema);
userSchema.plugin(uniqueValidator, {
    message: '{PATH} Already in use'
});

Let me explain the code snippet. We start by requiring mongoose, bcrypt, jsonwebtoken and mongoose-unique-validator  in our user model, after doing that, we create a new instance of our mongoose schema which takes in an object which defines the property of the user schema.
Mongoose converts the schema into a document in the database and those properties will be converted into fields in our document.
After defining the instance of the scehma, we define 3 methods:

  • The first method hashPassword will take in the user’s plain password in the params, hash the plain password and return the hashed password.
  • The second method compareUserPassword will aid use to compare the user’s password. This method will take in the users inputted a plain password, hash it and compare it with what is in the database. If it matches it returns true, if it doesn’t it returns false.
  • The third method generateJwtToken will generate a new token for a user when the login.

After writing this method we then define a custom error message for the mongoose unique validator in case we try to use the same email in the database.

Create the necessary routes

Now let’s create our routes. Below is the list of endpoints we will be creating:

  • HTTP POST /register  —— Register User.
  • HTTP POST /login —— Allow users to login.

Let’s start by creating a user’s route. Head over to /userRoute.js and write the following code:


const express = require("express");
const router = express.Router();
const userController = require("./userController");


router.post("/register", userController.registerNewUser);
router.post("/login", userController.loginUser);


module.exports = router;

Here we defined two routes, The first one to create a new user while the second one to log the user in. The routes call a method in our controller file which we are yet to define. By default, we will get an error on our console telling us that we haven’t defined this method yet. What we now have to do is to register our root route in our src/index.js file:


const userRoutes = require("./account/userRoute"); //bring in our user routes
app.use("/user", userRoutes);

It’s a good practice to separate your business logic from your route file.
Let define this method in our userController.js file. We will start defining the registerNewUser method


const User = require("./userModel");

exports.registerNewUser = async (req, res) => {
    try {
        let user = new User({
            name: req.body.name,
            phone_number: req.body.phone_number,
            email: req.body.email
        })
        user.password = await user.hashPassword(req.body.password);
        let createdUser = await user.save();
        res.status(200).json({
            msg: "New user created",
            data: createdUser
        })
    } catch (err) {
        console.log(err)
        res.status(500).json({
            error: err
        })
    }
}

We start by creating a new instance of User model. We bring in the method that hashes the user password from the userModel.js file and then call the save() method to save it on our database.


Now, let’s test it using Postman.POSTMAN is currently one of the most popular tools used in API testing endpoints.

Ensure that the type of request being sent is a POST request and the URL is localhost:4000/user/register. Ensure that you provide the required fields which are name, email, phone_number, and password like we defined them in the user model. Then also ensure that the body type is set to JSON.
Now let’s write the login method and test it. Below is the code snippet:


exports.loginUser = async (req, res) => {
    const login = {
        email: req.body.email,
        password: req.body.password
    }
    try {
        let user = await User.findOne({
            email: login.email
        });
        //check if user exit
        if (!user) {
            res.status(400).json({
                type: "Not Found",
                msg: "Wrong Login Details"
            })
        }
        let match = await user.compareUserPassword(login.password, user.password);
        if (match) {
            let token = await user.generateJwtToken({
                user
            }, "secret", {
                expiresIn: 604800
            })
            if (token) {
                res.status(200).json({
                    success: true,
                    token: token,
                    userCredentials: user
                })
            }
        } else {
            res.status(400).json({
                type: "Not Found",
                msg: "Wrong Login Details"
            })
        }
    } catch (err) {
        console.log(err)
        res.status(500).json({
            type: "Something Went Wrong",
            msg: err
        })
    }
}

First, we define the login object which contains inputted email and password and then query our database for the user that has the same email as the inputted email. We now check if the user exists or not. If the user doesn’t exist it throws an error but if the user exists it now calls the comparePassword method that we defined earlier in our userModel.js file. The method compares the password if it doesn’t match it throws an error but if it does match its calls the generateJwtToken the method that we defined in the useModel.js file to generate the Jwt token. This token is what we will be using for our validation. But before that, we need to create a middleware that checks for this token.
Now, let’s test the endpoint:

The endpoint returns the jwt token alongside the users credentials as we stated in the code.

Create the auth middleware.

Express middleware are functions that execute during the lifecycle of a request to the Express server. Each middleware has access to the HTTP request and response for each route (or path) it’s attached to. We want to check if a person who is trying to access a specific resource is authorized to access it. That is where we take advantage of middleware.
Head over to src/middleware/auth and write the following code:

const jwt = require("jsonwebtoken");
module.exports = (req, res, next) => {
  try {
    const token = req.headers.authorization.replace("Bearer ", "");
    console.log(token);
    const decoded = jwt.verify(token, "secret");
    req.userData = decoded;
    // console.log(req.userData);
    next();
  } catch (err) {
    return res.status(401).json({
      message: "Authentification Failed"
    });
  }
};

Now to use this auth middleware, we are going to go back to the user route file, import our auth middleware by requiring it at the top of the file just after requiring the user controller. We will define another route that will just return “HELLO WORLD”.This route is going to be protected, meaning that only logged in users can have access to this. So let’s define another route:


router.get("/data", userController.defineDummyData)

and then define the defineDummyData method:


exports.defineDummyData = async (req, res) => {
    res.json({
        message: "Hello World"
    })
}

Now lets test this end point:

To use the middleware we will modify the data route to :


router.get("/data", auth, userController.defineDummyData)

With this we just registered the middleware in that endpoint.If we try accessing this route we will get this:

So to fix this we have to do is go to the “Authorization” section in postman and then click on the type of authentication, We will use the Bearer Token type. What we have to do is to copy our token gotten when we and then paste it in the input token field and then try to send a request to the user/me route again.

Notice the “Authorization” tab is selected and in there, Bearer Token is chosen from the drop-down and on the right, a token is provided.With that any user that isn’t logged in can have access to that data.So let’s dive into our frontend.

Setting up the Frontend

  • Create a new Angular project using the Angular CLI.
  • Create the user interface(UI) for our application.
  • Create services for the backend service.
  • Configure the authentication service using our backend API.
  • Add Interceptors to the application

Create a new Angular project using the Angular CLI

Now we need to move into the client directory that we created earlier and create a new Angular project.Incase you don’t have the angular CLI globally installed in your development machine, open up your terminal and type:


npm install -g @angular/cli

But if you already have the angular cli installed you can skip that step and just move into the project directory to setup a new angular project.Open your terminal on your desktop and type this commands:

cd desktop
cd angular-auth && cd client
ng new angular-auth

The ng new angular-auth command will create a new angular project and  prompts you for information about features to include in the initial app.

Type y to add angular routing in our application.This will help us have access to other pages.

Select CSS as the default stylesheet format for the application. Feel free to choose any CSS stylesheet of your choice but for this article, we will use CSS.
Selecting all this will create generate all the files for our angular project. When it is done setting up the project you should see this in your terminal:

Now to run the application run this on the termainl:

cd angular-auth
code . && ng serve --open

The ng serve command launches the server, watches your files, and rebuilds the app as you make changes to those files and the --open flag will open your browser to http://localhost:4200/ where your application is being hosted.

Create the user interface(UI) for our application.

For this article, we will be using Angular Material for our user interface. To add that package to our angular project we will open up our terminal and type the following:


ng add @angular/material

Running this command will prompt some questions on what we need to setup.Select the following items:

After selecting this, it will add Angular material to our project. Now that we have Angular material installed added in our project, we have to now clean up the UI and create new components.
Let’s head over to src/app/app.component.html and replace the codes there with this:


<router-outlet></router-outlet>

Doing this will result to a blank page on our browser.Now we need to create 3 components for our application:

  • The first component will be the login component
  • The second will be the create user component
  • The third will be the where we will display the data gotten from our backend.

To create this component open up the terminal and type the following:

ng g c components/login
ng g c components/create
ng g c components/home

This command will create a components directory and add this 3 components inside it. After doing this let’s configure the routes so that we can navigate through these components. We will now head over to the src/app/app-routing.module.ts  file and import those 3 components that we created:


import { LoginComponent } from './components/login/login.component';
import { CreateComponent } from './components/create/create.component';
import { HomeComponent } from './components/home/home.component';

And the define the routes inside the Routes array:

const routes: Routes = [
  {
    path: 'login',
    component: LoginComponent
  },
  {
    path: 'sign-up',
    component: CreateComponent
  },
  {
    path: '',
    component: HomeComponent
  }
];

Now we can navigate through our components. Test your routes to confirm its working by trying to access the routes you just defined. Now let’s create the user interface for the application.
Before we continue always remember that before you try using an Angular material component you have to first of all register it in the app.module.ts file.


Now, let’s import all the Angular material components that we will be using in our project. To do that add the following imports in your app.modules.ts file:


import { MatButtonModule } from '@angular/material/button';
import { MatInputModule } from '@angular/material/input';
import { MatCardModule } from '@angular/material/card';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatGridListModule } from '@angular/material/grid-list';
import {MatSnackBarModule} from '@angular/material/snack-bar';

And the register then in the import array:

imports: [ othermodules,MatButtonModule, MatInputModule, MatCardModule, MatFormFieldModule,MatGridListModule,MatSnackBarModule]

Now that we have imported all the components that we will need we can now define our user interfaces.

So modify the login component to this:


<section>
  <mat-grid-list cols="3">
    <mat-grid-tile colspan="3">
      <mat-card>
        <mat-card-header color="primary">
          <mat-card-title>Login Form</mat-card-title>
        </mat-card-header>
        <form>
          <mat-form-field style="width:400px !important">
            <mat-label>Email</mat-label>
            <input matInput>
          </mat-form-field>
          <br>
          <mat-form-field style="width:400px !important">
            <mat-label>Password</mat-label>
            <input matInput>
          </mat-form-field>
          <br>
          <button mat-raised-button color="primary">Submit</button>
        </form>
      </mat-card>
    </mat-grid-tile>
  </mat-grid-list>
</section>

And the register component to this:


<section>
  <mat-grid-list cols="3">
    <mat-grid-tile colspan="3">
      <mat-card>
        <mat-card-header color="primary">
          <mat-card-title>Signup Form</mat-card-title>
        </mat-card-header>
        <form>
          <mat-form-field style="width:400px !important">
            <mat-label>Name</mat-label>
            <input matInput>
          </mat-form-field>
          <br>
          <mat-form-field style="width:400px !important">
            <mat-label>Email</mat-label>
            <input matInput>
          </mat-form-field>
          <br>
          <mat-form-field style="width:400px !important">
            <mat-label>Phone</mat-label>
            <input matInput>
          </mat-form-field>
          <br>
          <mat-form-field style="width:400px !important">
            <mat-label>Password</mat-label>
            <input matInput>
          </mat-form-field>
          <br>
          <button mat-raised-button color="primary">Submit</button>
        </form>
      </mat-card>
    </mat-grid-tile>
  </mat-grid-list>
</section>

And the home component will have this:


<mat-card>
  <mat-card-header color="primary">
    <mat-card-title>If You see This then you are authenticated</mat-card-title>
  </mat-card-header>
</mat-card>

And here is the result:

Login

Angular Authentication

Sign-up:

Angular Authentication

Home:

Now that we have our user interface ,lets create services for the backend endpoint.

Create services for the backend service.

We need to create services that will handle all our HTTP request. But before we do that let’s register our base URL in our src/environments/environment.ts file by adding this to the environment object:


baseURL:'http://localhost:4000/'

After doing that let use the cli to create a service for our http request.To do that type:


ng g s services/user/user

This command will create a services directory inside the app directory. The services directory will contain a user directory, this is where we will be making all our requests to our backend. Now open up the user.service.ts file and replace the codes there with this:


import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { environment } from '../../../environments/environment';
@Injectable({
  providedIn: 'root'
})
export class UserService {
  createNewUser(payload) {
    return this.http.post(`${environment.baseURL}user/register`, payload);
  }
  userLogin(payload) {
    return this.http.post(`${environment.baseURL}user/login`, payload);
  }
  getProtectedData() {
    return this.http.get(`${environment.baseURL}user/data`);
  }
  constructor(private http: HttpClient) {}
}

Here we bring in the custom Angular HTTP handler and also the environment object that we created. We then register the httpClient module in the constructor so that we can have access to it using this  keyword.We then define methods that will use the HTTP module to make a request to our backend which will return a response from our backend.Now that we have created these methods we can now use it in our components.
Let’s implement the “sign up” feature.Let start by bringing in Angulars formModule  and ReactiveFormsModule by importing them in our app.module.ts file and then register them in the import array.


import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';

imports:[othermodules,FormsModule,ReactiveFormsModule,HttpClientModule]

Now let head over to the src/app/components/create/create.component.ts and replace the codes there with:


import { Component, OnInit } from '@angular/core';
import { FormGroup, FormControl } from '@angular/forms';
import { Router } from '@angular/router';
import { HttpErrorResponse } from '@angular/common/http';
import { UserService } from '../../services/user/user.service';
import { MatSnackBar } from '@angular/material/snack-bar';

@Component({
  selector: 'app-create',
  templateUrl: './create.component.html',
  styleUrls: [ './create.component.css' ]
})
export class CreateComponent implements OnInit {
  constructor(private User: UserService, private router: Router,private snackBar: MatSnackBar) {}
  signupForm = new FormGroup({
    email: new FormControl(''),
    phone_number: new FormControl(''),
    name: new FormControl(''),
    password: new FormControl('')
  });
  ngOnInit() {}
}

Here we bring in formGroup and the formControl module, This is what we will be using to handle our HTML form elements. We bring in the Router module, we will use the module to navigate to the login component on the creation of an account. We bring in the HttpErrorResponse module which will be used to handle all errors that may occur in regards to our HTTP service. We then import the service that we created earlier.We then register our user service and the router module in the constructor. After doing that we register our form elements using the formGroup module that we registered.
We now need to configure the form group element in our HTML file, To do that replace the HTML file with this:


<section>
  <mat-grid-list cols="3">
    <mat-grid-tile colspan="3">
      <mat-card>
        <mat-card-header color="primary">
          <mat-card-title>Signup Form</mat-card-title>
        </mat-card-header>
        <form [formGroup]="signupForm" (ngSubmit)="createUser()">
          <mat-form-field style="width:400px !important">
            <mat-label>Name</mat-label>
            <input formControlName="name" matInput>
          </mat-form-field>
          <br>
          <mat-form-field style="width:400px !important">
            <mat-label>Email</mat-label>
            <input formControlName="email" matInput>
          </mat-form-field>
          <br>
          <mat-form-field style="width:400px !important">
            <mat-label>Phone</mat-label>
            <input formControlName="phone_number" matInput>
          </mat-form-field>
          <br>
          <mat-form-field style="width:400px !important">
            <mat-label>Password</mat-label>
            <input type="password" formControlName="password" matInput>
          </mat-form-field>
          <br>
          <button type="submit" mat-raised-button color="primary">Submit</button>
        </form>
      </mat-card>
    </mat-grid-tile>
  </mat-grid-list>
</section>

Basically we are registering the form group element in the input fields.We add a submit event listener that gets called when our form gets submitted.Now test our work , lets try getting all the values that we have inputted in the input fields.To do that lets define the createUser method and log the value.:


createUser() {
    console.log(this.signupForm.value);
  }

Now fill the form with random data and click on the submit button ,doing that you will see the inputted value logged on the console.

Now that we are sure that we are getting back the inputted values, let’s implement the createUser function. To do that modify the function to this:


createUser() {
    this.User.createNewUser(this.signupForm.value).subscribe(
      (data: any) => {
        console.log(data);
        this.router.navigate([ '/login' ]);
      },
      (err: HttpErrorResponse) => {
        if (err.error.msg) {
          this.snackBar.open(err.error.msg, 'Undo');
        } else {
          this.snackBar.open('Something Went Wrong!');
        }
      }
    );
  }

When the form is submitted the createNewUser method that we defined in the user service is called if the creation was successfully it navigate to the login component if there is any error it logs the errors. Be sure that your backend server is running. Now test this and ensure that it’s working.


Now let’s implement the login feature. We are going to do basically the same thing. Use the steps we took in the create component to configure the modules in the login component. Replace the code in the login.component.ts file with this:


import { Component, OnInit } from '@angular/core';
import { FormGroup, FormControl } from '@angular/forms';
import { Router } from '@angular/router';
import { HttpErrorResponse } from '@angular/common/http';
import { UserService } from '../../services/user/user.service';
import { MatSnackBar } from '@angular/material/snack-bar';

@Component({
  selector: 'app-login',
  templateUrl: './login.component.html',
  styleUrls: [ './login.component.css' ]
})
export class LoginComponent implements OnInit {
  constructor(private User: UserService, private router: Router,private snackBar: MatSnackBar) {}
  loginForm = new FormGroup({
    email: new FormControl(''),
    password: new FormControl('')
  });
  loginUser() {
    console.log(this.loginForm.value);
  }
  ngOnInit() {}
}

And modify your login.compoent.html to this:


<section>
  <mat-grid-list cols="3">
    <mat-grid-tile colspan="3">
      <mat-card>
        <mat-card-header color="primary">
          <mat-card-title>Login Form</mat-card-title>
        </mat-card-header>
        <form [formGroup]="loginForm" (submit)="loginUser()">
          <mat-form-field style="width:400px !important">
            <mat-label>Email</mat-label>
            <input formControlName="email" matInput>
          </mat-form-field>
          <br>
          <mat-form-field style="width:400px !important">
            <mat-label>Password</mat-label>
            <input type="password" formControlName="password" matInput>
          </mat-form-field>
          <br>
          <button type="submit" mat-raised-button color="primary">Submit</button>
        </form>
      </mat-card>
    </mat-grid-tile>
  </mat-grid-list>
</section>

Now fill the form with dummy data and click on the submit button, open up the console and you will see the inputted values.To implement the login functionality replace the loginUser method with this:


loginUser() {
    this.User.userLogin(this.loginForm.value).subscribe(
      (data: any) => {
        let token = data.token;
        localStorage.setItem('Token', token);
        this.router.navigate([ '/' ]);
      },
      (err: HttpErrorResponse) => {
        console.log(err.error);
        if (err.error.msg) {
          this.snackBar.open(err.error.msg, 'Undo');
        } else {
          this.snackBar.open('Something Went Wrong!');
        }
      }
    );
  }

When the form is submitted the userLogin method that we defined in the user service is called , if the login details are correct it generates the Jwt token and stores it in localstorage and then navigates the user to the / route.Now fill the form with the details you used in the sign up process.

Implementing Route Guard

The Angular router’s navigation guards allow to grant or remove access to certain parts of the navigation based on a  certain condition. The CanDeactivate guard, even allows you to prevent a user from accidentally leaving a component with unsaved changes.
Let’s guard our home page to only be accessed by authenticated users.We will start by creating a guard by running this on our terminal:


ng g g guard/auth

This will prompt us to choose which interfaces we would like to implement:

Choose the canActivate by using the spacebar on your keyboard to select.This controls if a route can be activated.This will create a guard directory inside the src/app directory which contains a auth.guard.ts file. This is where we will define our guard.let’s get down to securing our home page using the JSON Web Token that was generated at login. Now replace the code in the auth.guard.ts file with this:


import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree, Router } from '@angular/router';
import { Observable } from 'rxjs';
@Injectable({
  providedIn: 'root'
})
export class AuthGuard implements CanActivate {
  constructor(private router: Router) {}
  canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
    if (localStorage.getItem('Token') != null) return true;
    this.router.navigate([ '/login' ]);
    return false;
  }
}

Here we are checking if the value of Token in our local storage is null it returns false and then redirects the user to the login page, if it has a value then it returns true. After doing this we have to register this guard in our app-routing.module.ts file, so that we can protect the home page.

Now import the authGuard module in the  app-routing.module.ts file :


import { AuthGuard } from './guard/auth.guard';

And then modify the home route to this:


{
        path: '',
        canActivate: [ AuthGuard ],
        component: HomeComponent
    }

Here we register the auth guard in this route by passing the canActivate property to the authGuard module.
To test if this working, open up the application tab in your browser and delete the token in your local storage and then refresh the page. This will automatically take you to the login page. If you try accessing the login home page without logging in it will redirect you back to the login page.

Adding HTTP Inteceptors

We will use interceptors to set default headers on outgoing requests. We want to set the JWT as our default authorization headers so that we can have access to the user/data route that we created in our backend service.
Create a folder under your app folder name as interceptor And create a new file as httpconfig.interceptor.ts the under interceptor folder. Import the following dependencies into your httpconfig.interceptor.ts file and add the following codes:


import { Injectable } from '@angular/core';
import { HttpEvent, HttpInterceptor, HttpHandler, HttpRequest } from '@angular/common/http';
@Injectable({
    providedIn: 'root'
})
export class HttpConfigInterceptor implements HttpInterceptor {
    constructor() {}
    intercept(req: HttpRequest<any>, next: HttpHandler) {
        // Get the auth token from  localstorage.
        const authToken = localStorage.getItem('Token');
        // Clone the request and replace the original headers with
        // cloned headers, updated with the authorization.
        const authReq = req.clone({
            headers: req.headers.set('Authorization', authToken)
        });
        // send cloned request with header to the next handler.
        return next.handle(authReq);
    }
}

We get the saved token from our localstorage and define the req.clone which clones the request and replace the original headers with cloned headers, updated with the authorization.  After doing this head over to the components/home/home.component  replace the code there with this:


import { Component, OnInit } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { UserService } from '../../services/user/user.service';
imports: [ MatButtonModule ];
@Component({
    selector: 'app-home',
    templateUrl: './home.component.html',
    styleUrls: [ './home.component.css' ]
})
export class HomeComponent implements OnInit {
    constructor(private user: UserService) {}
    getProtectedData() {
        this.user.getProtectedData().subscribe((data: any) => console.log(data));
    }
    ngOnInit() {
        this.getProtectedData();
    }
}

We need to register our interceptor in the app.module.ts file.To do that we have to import the following:


import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { HttpConfigInterceptor } from '../../src/app/interceptor/httpconfig.interceptor';

And then register it int providers array:


providers: [
        {
            provide: HTTP_INTERCEPTORS,
            useClass: HttpConfigInterceptor,
            multi: true
        }
    ],

Now if we go back to our home route we will see this in our console:

You could display the message in your user interface if you want to.
With that, we have implemented the HTTP interceptor.

Implementing Logout

Now let implement the logout functionality. This will basically delete the Token in our localstorage and then navigate the user back to the login page. For navigating back to the login page we need to import the Router modules and then register in the constructor:


import { Router } from '@angular/router';
constructor(private user: UserService, private router: Router) {}

So ,create a button in the home.component  file and add a click event listener that calls a function logout

 <button type="submit" (click)="logout()" mat-raised-button color="primary">Logout</button>

And then the logout function:


logout() {
        localStorage.removeItem('Token');
        this.router.navigate([ '/login' ]);
    }

Clicking the logout button will now navigate you to the login page.If you try accessing the home page without logging in it will redirect you back to the login page.

Conclusion

In this article, we learnt how to add guards to our angular routes to prevent unauthenticated users from accessing some certain routes. We also learned how to write HTTP interceptors for our HTTP request to pass Authorization headers. For source code click here


Share on social media

//