AppDividend
Latest Code Tutorials

Angular 10 Tutorial: Angular JWT Authentication Example

2

Authentication typically consists of a user entering using a username or email and a password and then being granted access to different resources or services.

Authentication in Angular 10, by its very nature, relies on keeping the state of the User. This seems to contradict a fundamental property of HTTP, which is a stateless protocol.

JSON Web Token (JWT) provides a way to solve this issue. Your Angular app can talk to a backend that produces a token. The Angular app can then pass that token in an Authorization header to the backend to prove they are authenticated and needs access to the particular route or resources. The backend should verify the JWT and grant access based on its validity.

Then on the frontend, we set the guard for a particular access point. So, if the User is authenticated, it can access the specific resources; otherwise, it won’t be able to access.

What is JWT Authentication

When a user logs into service, the server checks for the User’s credentials, if username and password match then, the server encodes the key user data, such as a user ID or the User’s email address into a JSON string.

The string is then signed using the secret key. This data is the JSON Web Token. It can be sent back to the client and used by the client to authenticate itself.

If the server can validate the token with the appropriate key, it can be sure that the authentication server generated it. But it can’t be forged because only the authentication server knows the private key.

Authentication can be provided by a service that is separate from the service wanting to restrict access.

What can Angular JWT Authentication do?

Angular Authentication should be able to do the following functionalities.

  1. Users can register via Angular forms. (Template or Reactive Forms)
  2. After register, the User can be logged in to the application.
  3. If email and Password are correct, then the backend should generate a token and send back to the client.
  4. The token should be saved in localStorage and can be able to retrieve whenever possible.
  5. Users can set the token on the header, so after logged in, the token can be verified to access private routes.
  6. Users can set an auth guard so that all the private routes should be protected based on the authorized users.

Angular 10 Tutorial

If you are taking this tutorial, then please do one thing and update all the different dependencies of this project as soon as possible Otherwise, you might face some issues while taking this tutorial.

It is not mandatory to update all the tech stacks, but its good to use the latest version in this Angular 10 Tutorial.

  1. Angular 10 (Check out how to update Angular CLI version 10)
  2. Node.js 14.4.0(If your version is even number, then it is okay; otherwise, update it to even number because you can face some issues while running backend) and NPM or Yarn.
  3. MongoDB Database.
  4. Visual Studio Code.

We will create a backend using Node.js and Express framework.

This project is divided mainly into two parts.

  1. Frontend (Angular)
  2. Backend (Node)

We will start this tutorial by Installing Angular 10, and then immediately, we will build the Node.js server.

Step 1: Setup Angular 10 CLI

To update your Angular CLI, check out the Angular CLI 10 Upgrade tutorial.

To install Angular CLI 10 in your system, type the following command.

// For Mac and Linux

sudo npm install -g @angular/cli

// For Windows

Open CMD in Administrator mode and hit the following command.

npm install -g @angular/cli

Now, run the following command to check the Angular CLI version.

ng version

Step 2: Initializing a New Angular 10 Project

Type the following command to create the new Angular 10 project.

ng new angularjwtauth

 

Angular 10 Project

We have enabled App routing, and we will use CSS in our project. You can use SCSS instead of CSS.

The CLI will ask you two questions like If Would you like to add Angular routing? Type y for Yes, and the second question is which stylesheet format would you like to use? Choose CSS. You can choose anyone based on your requirements.

This will instruct the CLI to set up routing in our project automatically, so we’ll only need to add the routes for our angular components to implement navigation in our application.

Now, go inside the angularjwtauth folder and run the following command to start the angular project.

cd angularjwtauth
ng serve -o

Using the “-o” parameter will automatically open this Angular 9 app in the default browser.

The default URL is: http://localhost:4200

Now, keep the server open and in the terminal open a new tab and go inside our angularjwtauth folder.

Step 3: Create a Node.js backend server

To create the Node.js server, first, create a new folder inside the angularjwtauth folder called auth.

The auth folder will be our main node.js folder in which we write the serverside authentication code.

So, your angularjwtauth folder looks like below.

 

node.js backend

Now, go inside the folder and initialize the package.json file by the following command.

npm init -y

Next, we will install all the serverside dependency that requires to create a JWT authentication system.

npm install bcryptjs body-parser cors express jsonwebtoken mongoose validator --save

Now, install the nodemon server, which is dev-dependency in our project.

npm install nodemon --save-dev

bcryptjs

The bcrypt is a secure way to store passwords in the Database.

The general workflow for account registration and Authentication in a hash-based account system is as follows:

  1. The User can create an account using an email or username and Password.
  2. Their Password is hashed and stored in the Database. At no point, the Password is the plain-text (unencrypted) password ever written to the hard drive.
  3. When the user attempts to log in, the hash of the password they entered is checked against the hash of their real Password (retrieved from the Database).
  4. If their hashes match, then the user is granted access. If not, then the User is told they entered invalid login credentials.

body-parser

It is a Node.js body parsing middleware. The body-parser module parses an incoming request bodies in the middleware before your route handlers, available under the req.body property.

The body-parser object exposes various factories to create middlewares.

All middlewares will populate the req.body property with a parsed body when the Content-Type request header matches the type option, or the empty object ({}) if there is nobody to parse, then the Content-Type is not matched, or an error occurred.

cors

CORS is a Node.js package for providing the Connect/Express middleware that can be used to enable CORS with various options. If you want to restrict AJAX access to a single origin, you can use the origin option.

app.use(cors({
  origin: 'http://angularjwtauth:4200'
}));

express

Express.js, or only Express, is the web application framework for Node.js, released as free and open-source software. Express is the minimal and flexible Node.js web application framework that provides a strong set of unique features for web and mobile applications.

jsonwebtoken

The jsonwebtoken is the implementation of JSON Web Tokens in Node.js. So when the user successfully logged in, this module will generate a token based on userid and username and sent back to the client.

mongoose

Mongoose provides the straight-forward, schema-based solution to model your application data. It includes an inbuilt type casting, validation, query building, business logic hooks, and more, out of the box.

nodemon

Nodemon is the utility that will monitor for any changes in your source and automatically restart your server. It is perfect for web application development in node.js.

Now, create a new file called server.js inside the auth folder and write the following code.

// server.js

const express = require('express');
const mongoose = require('mongoose');
const bodyParser = require('body-parser');
const cors = require('cors');

const app = express();

app.get('/', function (req, res) {
  res.send('hello');
});

const PORT = process.env.PORT || 5000;

app.listen(PORT, () => {
  console.log(`Server is running on PORT ${PORT}`);
});

In this code, first, we have imported four modules and then create an instance of Express called app.

Then we have defined a port of our application, which is 5000.

So, our Angular app will run on 4200, and node.js will run on 5000 port.

Then we spun up the server at 5000 port.

Now, to start a node server, type the following command. Please make sure that you are inside the auth folder.

nodemon server

In the terminal, you will see something like below.

➜  auth git:(master) ✗ nodemon server
[nodemon] 2.0.4
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): *.*
[nodemon] watching extensions: js,mjs,json
[nodemon] starting `node server.js`
Server is running on PORT 5000

That is it. You have successfully started the node.js server.

Step 4: Connect the Node.js app to MongoDB 

If you have not installed MongoDB, then install the MongoDB Community Edition.

First, start a mongodb server by the following command.

mongod

So, this will be your third server that will be running right now.

Then inside the auth folder, create a file called DB.js and add the following object.

// DB.js

module.exports = {
  DB: 'mongodb://localhost:27017/angularauth'
}

Although you can use the env file for credentials, for this tutorial, I am not doing that because of the localhost. Still, for the production version, you must have to use environment variables to hide your database credentials and security purpose.

Now, we have to add the following code inside the server.js file.

// server.js

const config = require('./DB');
mongoose.set('useNewUrlParser', true);
mongoose.set('useUnifiedTopology', true);
mongoose.set('useFindAndModify', false);
mongoose.set('useCreateIndex', true);

mongoose.connect(config.DB).then(
  () => { console.log('Database is connected') },
  err => { console.log('Can not connect to the database' + err) }
);

Now, if your mongodb server is running successfully, then when you save the above file, your terminal looks like below.

 nodemon server
[nodemon] 2.0.4
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): *.*
[nodemon] watching extensions: js,mjs,json
[nodemon] starting `node server.js`
Server is running on PORT 5000
Database is connected

You can see that we have successfully connected to our MongoDB database.

Step 5: Create an MVC Structure in the backend

Model–view–controller is a software design pattern commonly used for developing user interfaces that divide the related program logic into three interconnected elements. This is done to separate internal representations of information from the ways information is presented to and accepted by the User.

In this example, we don’t need to create View because Angular already handles it. So, we just have to manage models, routes, and controllers.

Now, inside the auth folder, create the following three folders.

  1. models
  2. routes
  3. controllers

Inside the models folder, create a file called User.js

Inside the routes folder, create a file called UserRoute.js

Inside the controllers folder, create a file called UserController.js

In the User.js file, we will define a user schema. It consists of a username, email, Password, and passwordConfirmation. We will also specify validation for the same fields.

Step 6: Define a user schema.

Write the following code inside the User.js file.

// User.js

const mongoose = require('mongoose')
const Schema = mongoose.Schema

const userSchema = new Schema({
  username: {
    type: String,
    min: [4, 'Too short, min 4 characters are required'],
    max: [32, 'Too long, max 16 characters are required']
  },
  email: {
    type: String,
    min: [4, 'Too short, min 4 characters are required'],
    max: [32, 'Too long, max 16 characters are required'],
    lowercase: true,
    unique: true,
    required: 'Email is required',
    match: [/^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/]
  },
  password: {
    type: String,
    min: [4, 'Too short, min 4 characters are required'],
    max: [32, 'Too long, max 16 characters are required'],
    required: 'password is required'
  },
  passwordConfirmation: {
    type: String,
    min: [4, 'Too short, min 4 characters are required'],
    max: [32, 'Too long, max 16 characters are required']
  }
});

module.exports = mongoose.model('User', userSchema)

In this code, first, we have imported mongoose schema.

Then we instantiate the userSchema bypassing the fields in the constructor.

The user schema consists of four fields.

  1. username
  2. email
  3. password
  4. passwordConfirmation

We have defined each field’s data type and its validation rules.

In the last line, we have exported the User module so that we can import it in any other modules.

Step 7: Define a register and login method in UserController

Inside the UserController.js file, we have to create two methods.

  1. register
  2. login

Write the following code inside the UserController.js file.

// UserController.js

const User = require('../models/User')

exports.register = function (req, res) { }
exports.login = function (req, res) { }

We have an imported User model because in the future, when we need to insert the user entry in the Database.

Step 8: Define routes in the UserRoute.js file.

Write the following code inside the UserRoute.js file.

// UserRoute.js

const express = require('express')
const user = require('../controllers/UserController')
const router = express.Router()

router.post('/register', user.register)

router.post('/login', user.login)

router.get('/index', function (req, res) {
  res.json({ 'access': true })
})

module.exports = router

In this file, we have imported two modules.

  1. express for routing
  2. UserController

We have instantiated the router object and will use various methods like GET or POST or other HTTP request handling methods.

All the requests contain the form data if it is a POST request. We will parse the request through the body-parser module.

It is a route handling file on the server-side.

We defined three routes for register, login, and index.

When the request comes from the frontend, it will redirect to here, and based on the GET or POST and URI, and it will execute the controller function.

Step 9: Add CORS and body-parser middleware

We need the CORS module because the requests coming from the frontend is a different server, so due to CROSS SITE ORIGIN security, we need to enable for Angular server.

So, write the following code inside the server.js file.

// server.js

const express = require('express');
const mongoose = require('mongoose');
const bodyParser = require('body-parser');
const cors = require('cors');

const config = require('./DB');
const userRoute = require('./routes/UserRoute');

const PORT = process.env.PORT || 5000;

mongoose.set('useNewUrlParser', true);
mongoose.set('useUnifiedTopology', true);
mongoose.set('useFindAndModify', false);
mongoose.set('useCreateIndex', true);

mongoose.connect(config.DB).then(
  () => { console.log('Database is connected') },
  err => { console.log('Can not connect to the database' + err) }
);

const app = express();

app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());

let corsOptions = {
  origin: 'http://localhost:4200',
  optionsSuccessStatus: 200 // some legacy browsers (IE11, various SmartTVs) choke on 204
}

app.use(cors(corsOptions))
app.use('/api/users', userRoute);

app.listen(PORT, () => {
  console.log(`Server is running on PORT ${PORT}`);
});

So, this is a complete server.js file.

The cors middleware allows the server to respond to Cross-Origin Requests.

Then we have defined corsOptions object, in which we have passed origin property whose value is our angular server. So, if the request is coming from this angular server, then it will process otherwise cross-origin error will be thrown.

The ‘body-parser’ is needed to parse the HTTP request body and create an object that is attached to the request data.

We have imported the UserRoute module.

The express application attaches a router to the route /api/users. This router is defined in a separate file called UserRoute.js. It is an authentication route.

From Angular application, our request URI should be the following.

  1. POST: http://localhost:5000/api/users/register
  2. POST: http://localhost:5000/api/users/login
  3. GET: http://localhost:5000/api/users/index

Step 10: Complete User Register functionality

We have already defined a register and login function inside the UserController.js file.

Let’s write the code inside the register() function to save the User in MongoDB.

const User = require('../models/User')

// User register
exports.register = function (req, res) {
  const { username, email, password, passwordConfirmation } = req.body
  if (!email || !password) {
    return res.status(422).json({ 'error': 'Please provide email or password' })
  }

  if (password != passwordConfirmation) {
    return res.status(422).json({ 'error': 'Password does not match' })
  }
  User.findOne({ email }, function (err, existingUser) {
    if (err) {
      return res.status(422).json({ 'error': 'Oops! Something went Wrong' })
    }
    if (existingUser) {
      return res.status(422).json({ 'error': 'User already exists' })
    }
    else {
      const user = new User({
        username, email, password
      })

      user.save(function (err) {
        if (err) {
          return res.status(422).json({
            'error': 'Oops! Something went Wrong'
          })
        }
        return res.status(200).json({ 'registered': true })
      })
    }
  })
 }

// User login
exports.login = function (req, res) { }

In this code, first, we have extracted username, email, Password, and passwordConfirmation from req.body.

Then we checked validation for if email or Password is empty or not. And also, check if Password and passwordConfirmation are the same or not.

If any of the conditions do not meet, then it will return an error to the client. Otherwise, it will proceed further.

Then we are checking for the email if the email already exists in the Database. If so, then it will return with 422 code saying the User already exists.

Otherwise, we will create a new User and pass the username, email, and Password.

Now, here we don’t want to save the User’s Password in plain text. We have to encrypt the Password.

To do that, we need to write a hook function. We will write that function inside the User.js model file.

// User.js

const mongoose = require('mongoose')
const bcrypt = require('bcryptjs')
const Schema = mongoose.Schema

userSchema.pre('save', function (next) {
  const user = this
  bcrypt.genSalt(10, function (err, salt) {
    if (err) {
      return res.status(422).json({
        'error': 'There is an error while gensalt hash'
      })
    }
    bcrypt.hash(user.password, salt, function (err, hash) {
      if (err) {
        return res.status(422).json({
          'error': 'There is an error while password hash'
        })
      }
      user.password = hash
      next()
    })
  })
})

This pre() function hash our Password while registering a user.

The pre() function will automatically get called while the User is saving in the Database, and we are using the bcrypt library to create a password hash.

And then, the flow gets back to register() function, and if everything is fine, then the User will be registered in our application.

In short, the Password of a user is now hashed using the bcryptjs library. Only the hashed Password is stored in the Database. On success, the server responds with 200 status with message ‘registered: true’.

Once a user is registered, they need to be able to log on. This can be done in a separate route /api/users/login. This is where you will start using JSON Web Tokens.

So, here, the User registration in Node.js is complete. Let’s move on to the user login functionality.

Step 11: Complete User Login functionality

If the login is successful, then we need to send back a JWT token to the client.

If the login is failed, then we will send a proper message saying what went wrong.

Write the following code inside the login() function in the UserController.js file.

Before that, we need to define a secret key inside the DB.js file. This file already exports the DB credentials.

Now, we will add one more property called secret, which has the value which could not be revealed to the public.

So, writhe DB.js file like the following.

// DB.js

module.exports = {
  DB: 'mongodb://localhost:27017/angularauth',
  secret: 'your value here'
}

Here, you need to add your secret value. We will need this value while creating a JWT token.

Now, require the DB module inside the UserController.js file.

const User = require('../models/User')
const env = require('../DB')

Now, write the complete login() function.

// UserController.js

const User = require('../models/User') 
const env = require('../DB')
const jwt = require('jsonwebtoken')

exports.login = function (req, res) { 
  const { email, password } = req.body

  if (!email || !password) {
    return res.status(422).json({ 'error': 'Please provide email or password' })
  }
  User.findOne({ email }, function (err, user) {
    if (err) {
      return res.status(422).json({
        'error': 'Oops! Something went wrong'
      })
    }

    if (!user) {
      return res.status(422).json({ 'error': 'Invalid user' })
    }

    if (user.hasSamePassword(password)) {
      json_token = jwt.sign(
        {
          userId: user.id,
          username: user.username
        },
        env.secret,
        { expiresIn: '1h' })

      return res.json(json_token)
    }
    else {
      return res.status(422).json({ 'error': 'Wrong email or password' })
    }
  })
}

Here, you can see that we have imported the jwt package. It will help us to generate a JWT token.

First, we are extracting the email and Password from the request.

Then we are checking if email or Password is empty. If empty, then we will send an error response.

Then we are checking for email, and if the email is not found in the Database, then we will send a 422 response saying that Invalid User.

Because if the email is not found, then there is no need to check the Password because it does not make any sense.

If the User does exist, then now we will check for the Password.

You can see that we have used hasSamePassword() method on the User model, but we have not defined a function in the User model. So let’s do that first. Switch to the User.js model and define the hasSamePassword() function. We can do that by defining userSchema.methods object.

It will return a boolean value saying if the incoming Password is valid or not.

// User.js

userSchema.methods.hasSamePassword = function (password) {
  return bcrypt.compareSync(password, this.password)
}

The compareSync() function is provided by the bcrypt library, which will return true based on the stored database password and Password that comes with user requests.

If it returns true, then that means email and Password are valid, and User is authenticated. Now, it’s time to generate a user token.

So, our final User.js file looks like below.

// User.js

const mongoose = require('mongoose')
const bcrypt = require('bcryptjs')
const Schema = mongoose.Schema

const userSchema = new Schema({
  username: {
    type: String,
    min: [4, 'Too short, min 4 characters are required'],
    max: [32, 'Too long, max 16 characters are required']
  },
  email: {
    type: String,
    min: [4, 'Too short, min 4 characters are required'],
    max: [32, 'Too long, max 16 characters are required'],
    lowercase: true,
    unique: true,
    required: 'Email is required',
    match: [/^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/]
  },
  password: {
    type: String,
    min: [4, 'Too short, min 4 characters are required'],
    max: [32, 'Too long, max 16 characters are required'],
    required: 'password is required'
  },
  passwordConfirmation: {
    type: String,
    min: [4, 'Too short, min 4 characters are required'],
    max: [32, 'Too long, max 16 characters are required']
  }
});

userSchema.pre('save', function (next) {
  const user = this
  bcrypt.genSalt(10, function (err, salt) {
    if (err) {
      return res.status(422).json({
        'error': 'There is an error while gensalt hash'
      })
    }
    bcrypt.hash(user.password, salt, function (err, hash) {
      if (err) {
        return res.status(422).json({
          'error': 'There is an error while password hash'
        })
      }
      user.password = hash
      next()
    })
  })
})

userSchema.methods.hasSamePassword = function (password) {
  return bcrypt.compareSync(password, this.password)
}

module.exports = mongoose.model('User', userSchema)

Now, back to the UserController.js file’s login() function.

If the User provides the correct email and password, then we will generate a JWT token.

To create a JWT token, we will use the jwt.sign() method, which takes the following parameters.

  1. Object: It is a Javascript object containing the user id and username
  2. secret: Our secret, which we have defined in the DB.js file.
  3. expiresIn: The expire time in which the JWT token will expire.

Now in response, we will send this JWT token to the client.

If the username or password is invalid, then we will send an error message saying the wrong email or password.

Now, we have to add the following code inside the home.component.ts file.

// home.component.ts

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

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

  notify: string;

  constructor(private router: Router, private route: ActivatedRoute) { }

  ngOnInit(): void {
    this.route.queryParams.subscribe((params) => {
      const key1 = 'loggedin';
      if (params[key1] === 'success') {
        this.notify = 'You have been successfully loggedin. Welcome home';
      }
    });
  }

}

If the user is successfully login then we have to display the success message and to do that, we have to get the success message from the query string, and based on the condition, we will display the success message.

Now, write the following code inside the home.component.html file.

<!-- home.component.html -->

<div *ngIf="notify" class="alert alert-success container marge">
    {{ notify }}
</div>
<p class="container">home page!</p>

Also, write the following css code inside the home.component.css file.

.marge {
  margin-top: 1%
}

Now, you can see the message, if the user is logged in successfully. 

That is it for the login functionality.

Step 12: Define an Authentication Middleware

We can define an auth middleware inside the UserController.js file.

Auth middleware will protect the private route. If the user is not authenticated, then the auth middleware will prevent accessing the private route; otherwise, it will grant access.

Now, write the following code inside the UserController.js file.

// UserController.js

exports.authMiddleware = function (req, res, next) {
  const json_token = req.headers.authorization
  try {
    if (json_token) {
      const user = parseToken(json_token)
      User.findById(user.userId, function (err, user) {
        if (err) {
          return res.status(422).json({
            'error': 'Oops! Something went wrong'
          })
        }
        if (user) {
          res.locals.user = user
          next()
        }
        else {
          return res.status(422).json({ 'error': 'Not authorized user' })
        }
      })
    }
    else {
      return res.status(422).json({ 'error': 'Not authorized user' })
    }
  } catch (err) {
    res.status(403).json({
      success: false,
      message: err
    })
  }
}

function parseToken(token) {
  return jwt.verify(token.split(' ')[1], env.secret)
}

Now, let’s define a protected route inside the UserRoute.js file.

// UserRoute.js

const express = require('express')
const user = require('../controllers/UserController')
const router = express.Router()

const { authMiddleware } = require('../controllers/UserController')

router.post('/register', user.register)

router.post('/login', user.login)

router.get('/profile', authMiddleware, function (req, res) {
  res.json({ 'access': true })
})

module.exports = router

Here, we have defined a GET route /api/users/profile.

Now, this route is protected route by authMiddleware.

So, if the user is not logged in, then it won’t be able to access the route and redirect to the login page in clientside based on the response.

Right now, we are just responding with access: true, but we can here send the logged-in user’s data.

Now, let’s get back to the UserController’s authMiddleware() function.

When the request comes from clientside, it will try to extract the JWT token from the request header.

Now, the JWT token has a specific format in the header. The format is the following.

Bearer JWT_TOKEN

Now, we don’t need Bearer string, and we just want the JWT_TOKEN. So, we have created a function called parseToken(), which will return the JWT token and remove Bearer.

And then, we will verify that token using the jwt.verify() method. It will extract the user.id, and then we will check the user in the Database based on the user id.

If the user exists, then we store the user in res.locals. The res.locals is an object that contains response local variables scoped to the request, and therefore available only to the view(s) rendered during that request, and the request will continue to execute.

So, that is it for the Auth middleware. Our final UserController.js file looks like below.

// UserController.js

const User = require('../models/User')
const env = require('../DB')
const jwt = require('jsonwebtoken')

exports.register = function (req, res) {
  const { username, email, password, passwordConfirmation } = req.body
  if (!email || !password) {
    return res.status(422).json({ 'error': 'Please provide email or password' })
  }

  if (password != passwordConfirmation) {
    return res.status(422).json({ 'error': 'Password does not match' })
  }
  User.findOne({ email }, function (err, existingUser) {
    if (err) {
      return res.status(422).json({ 'error': 'Oops! Something went Wrong' })
    }
    if (existingUser) {
      return res.status(422).json({ 'error': 'User already exists' })
    }
    else {
      const user = new User({
        username, email, password
      })

      user.save(function (err) {
        if (err) {
          return res.status(422).json({
            'error': 'Oops! Something went wrong'
          })
        }
        return res.status(200).json({ 'registered': true })
      })
    }
  })
 }
exports.login = function (req, res) { 
  const { email, password } = req.body

  if (!email || !password) {
    return res.status(422).json({ 'error': 'Please provide email or password' })
  }
  User.findOne({ email }, function (err, user) {
    if (err) {
      return res.status(422).json({
        'error': 'Oops! Something went wrong'
      })
    }

    if (!user) {
      return res.status(422).json({ 'error': 'Invalid user' })
    }

    if (user.hasSamePassword(password)) {
      json_token = jwt.sign(
        {
          userId: user.id,
          username: user.username
        },
        env.secret,
        { expiresIn: '1h' })

      return res.json(json_token)
    }
    else {
      return res.status(422).json({ 'error': 'Wrong email or password' })
    }
  })
}

exports.authMiddleware = function (req, res, next) {
  const json_token = req.headers.authorization
  try {
    if (json_token) {
      const user = parseToken(json_token)
      User.findById(user.userId, function (err, user) {
        if (err) {
          return res.status(422).json({
            'error': 'Oops! Something went wrong'
          })
        }
        if (user) {
          res.locals.user = user
          next()
        }
        else {
          return res.status(422).json({ 'error': 'Not authorized user' })
        }
      })
    }
    else {
      return res.status(422).json({ 'error': 'Not authorized user' })
    }
  } catch (err) {
    res.status(403).json({
      success: false,
      message: err
    })
  }
}

function parseToken(token) {
  return jwt.verify(token.split(' ')[1], env.secret)
}

So, that is it for the server-side User authentication. Let’s get back to the client-side.

Step 13: Create Angular components

Components in Angular are the most basic UI building block of an Angular app. An Angular app contains a tree of Angular componentsAngular components are a subset of directives, always associated with a template.

Now, if you are inside the auth folder, then please go one step back and reach the root of the angular folder and type the following command one by one.

ng g c header  --skipTests=true
ng g c home    --skipTests=true
ng g c profile --skipTests=true
ng g c auth    --skipTests=true

We will put the navigation bar on the HeaderComponent.

The root URL component would be the HomeComponent.

The profile component will be our private component, which we can access if the user is authenticated.

The AuthComponent contains the following child components.

  1. RegisterComponent
  2. LoginComponent

We will go module wise from here. So register and login both functionalities we can count under auth. So, we will create the following three files for auth.

  1. Auth Module file
  2. Auth Service file
  3. Auth Guard file

Let’s hit the following command to create the above files.

To generate the Auth module file, type the following command.

ng g module auth

To generate the Auth service file, type the following command.

ng g s auth/auth --skipTests=true

To generate the Auth guard file, type the following command.

ng g guard auth/auth --skipTests=true
? Which interfaces would you like to implement? CanActivate
CREATE src/app/auth/auth.guard.ts (456 bytes)

You will ask for different options. Select CanActivate option.

Let’s create these two components, as well.

ng g c auth/register --skipTests=true
ng g c auth/login  --skipTests=true

Now, if you navigate to the auth folder inside the app folder, then you will see two more folders called register and login.

So, we have created almost all the files that we need in our frontend project.

If you see the app.module.ts file, then it will look like this. I have already imported AuthModule. So please copy the following code.

// app.module.ts

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

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { HeaderComponent } from './header/header.component';
import { HomeComponent } from './home/home.component';
import { ProfileComponent } from './profile/profile.component';

import { AuthModule } from './auth/auth.module';

@NgModule({
  declarations: [
    AppComponent,
    HeaderComponent,
    HomeComponent,
    ProfileComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    AuthModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

And your auth.module.ts file should be the following. If not, then copy the following code.

// auth.module.ts

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';

import { AuthComponent } from './auth.component';
import { RegisterComponent } from './register/register.component';
import { LoginComponent } from './login/login.component';

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

@NgModule({
  declarations: [
    RegisterComponent,
    LoginComponent
  ],
  imports: [
    CommonModule
  ],
  providers: [AuthService, AuthGuard]
})
export class AuthModule { }

All the auth related components like register and login component will be imported in this module file. We will also import AuthService and AuthGuard files as well.

This auth.module.ts file will be imported inside the app.module.ts file.

The next step will be to install Bootstrap 4 and then create a navigation bar.

Step 14: Install bootstrap and create navigation.

To install bootstrap in Angular 10, type the following command.

npm install bootstrap --save

After that, we have to include in our angular application. So open the angular.json file and add the following line under build >> options >> styles object.

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

Now, our angular application has access to bootstrap classes.

Okay, now open the header.component.html file and write the following code.

<!-- header.component.html -->

<nav class="navbar navbar-light navbar-expand-lg" style="background-color: #e3f2fd;">
  <div class="container">
    <a routerLink="/" class="navbar-brand" routerLinkActive="active" >AppDividend</a>
    <div class="collapse navbar-collapse" id="navbarSupportedContent">
      <ul class="navbar-nav ml-auto">
        <ng-container>
          <li class="nav-item">
            <a routerLink="/" class="nav-link" routerLinkActive="active">
            Login
            </a>
          </li>
          <li class="nav-item">
            <a routerLink="/" class="nav-link">
            Register
            </a>
          </li>
         </ng-container>
      </ul>
    </div>
  </div>
</nav>

Save the file and go to the app.component.html file and write the following code.

<div>
  <app-header></app-header>
</div>

Now, start the Angular development server, if you have not started by the following command.

ng serve -o

It will compile all the files and check for the errors. If everything is correct, then it will compile successfully and now go to http://localhost:4200.

You will see a navigation bar with login and register nav items.

Step 15: Define routes and child routes.

At the time of the creation of an angular app, we have enabled the routing, and that is why there is one file called app-routing.module.ts is created. Open that file in the code editor and add the following code.

// app-routing.module.ts

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

import { ProfileComponent } from './profile/profile.component';
import { HomeComponent } from './home/home.component';

const routes: Routes = [
  { path: '', component: HomeComponent },
  { path: 'profile', component: ProfileComponent }
];

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

Here, we have created two routes.

  1. First is HomeComponent, which is the root URI of our application.
  2. Second is ProfileComponent, which is to see the profile of the user. In the future, it will be guarded by Authentication. So, if the user is not signed in, then it will redirect back to the login page.

Now, write the following code inside the app.component.html file.

<div>
  <app-header></app-header>
  <router-outlet></router-outlet>
</div>

Save the file and go to the browser. You will see something like the below image.

 

Angular 10 Tutorial Example

That is it. You can now see that we have successfully rendered our HomeComponent on the root URI.

Now, let’s define routes for login and register components inside the auth.module.ts file.

// auth.module.ts

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';

import { Routes, RouterModule } from '@angular/router';

import { AuthComponent } from './auth.component';
import { RegisterComponent } from './register/register.component';
import { LoginComponent } from './login/login.component';

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

const routes: Routes = [
  {
    path: 'auth',
    component: AuthComponent,
    children: [
      { path: 'login', component: LoginComponent },
      { path: 'register', component: RegisterComponent }
    ]
  }
];

@NgModule({
  declarations: [
    RegisterComponent,
    LoginComponent
  ],
  imports: [
    RouterModule.forChild(routes),
    CommonModule
  ],
  exports: [RouterModule],
  providers: [AuthService, AuthGuard]
})
export class AuthModule { }

In this file, we have defined our routes and subroutes of authentication components.

Our main component will be an AuthComponent, and its child components are the following.

  1. LoginComponent
  2. RegisterComponent

So, if we want to access the Login and RegisterComponent, then our URI will be /auth/login and /auth/register.

Add these routes inside the header.component.html file.

<!-- header.component.html -->

<nav class="navbar navbar-light navbar-expand-lg" style="background-color: #e3f2fd;">
  <div class="container">
    <a routerLink="/" class="navbar-brand" routerLinkActive="active" >AppDividend</a>
    <div class="collapse navbar-collapse" id="navbarSupportedContent">
      <ul class="navbar-nav ml-auto">
        <ng-container>
          <li class="nav-item">
            <a routerLink="/auth/login" class="nav-link" routerLinkActive="active">
            Login
            </a>
          </li>
          <li class="nav-item">
            <a routerLink="/auth/register" class="nav-link">
            Register
            </a>
          </li>
         </ng-container>
      </ul>
    </div>
  </div>
</nav>

Here, we have added the <a routerLink=”/auth/login”> and <a routerLink=”/auth/register”>.

Now, to display the child component, we have to add the <router-outlet> inside the auth.component.html file.

<router-outlet></router-outlet>

Now, if you click on the Login link, you will see that LoginComponent will be rendered and the same for the Register link.

Step 16: Add Form to RegisterComponent

We will use a template-based form in Register Component. To work with Angular Forms, first, we have to import all the Form related modules inside the auth.module.ts file. So let’s do that.

// auth.module.ts

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

 imports: [
    RouterModule.forChild(routes),
    CommonModule,
    FormsModule,
    ReactiveFormsModule
  ],

Okay, now copy the following code and paste inside the register.component.ts file.

// register.component.ts

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

@Component({
  selector: 'app-register',
  templateUrl: './register.component.html',
  styleUrls: ['./register.component.scss']
})
export class RegisterComponent implements OnInit {

  formData: any = {};
  errors: any = [];

  constructor(private auth: AuthService, private router: Router) { }

  ngOnInit(): void {
  }

  register(): void {
    console.log(this.formData);
  }
}

In this code, we have defined two variables.

  1. formData: It can be an object of any data type.
  2. errors: It can be an array of any data type.

Then we have injected the AuthService and router in the constructor. So, we can use its properties and methods throughout the class.

And then, we have defined the register() method, which will call the AuthService’s register() function, which sends the POST request to the Node.js server. But right now, we have just logged the formData.

Now, write the following code inside the register.component.html file.

<!-- register.component.html -->

<div class="container">
    <div class="row">
      <div class="col-md-5">
        <h1 class="page-title">User Registration</h1>
        <form #registerForm="ngForm">
          <div class="form-group">
            <div *ngIf="errors.length > 0" class="alert alert-danger">
            <ul *ngFor = "let error of errors">
                <li>{{ error }}</li>
            </ul>
            </div>
            <label for="username">Username</label>
           <input type="text"
                   class="form-control"
                   [(ngModel)] = "formData.username"
                   name="username"
                   #username = "ngModel"
                   required>
          </div>
          <div *ngIf="username.invalid && (username.dirty || username.touched)"
            class="alert alert-danger">
            <div *ngIf="username.errors.required">
              Username is required
            </div>
          </div>

          <div class="form-group">
            <label for="email">Email</label>
            <input type="email"
                   class="form-control"
                   [(ngModel)] = "formData.email"
                   name="email"
                   #email = "ngModel"
                   required>
          </div>
          <div *ngIf="email.invalid && (email.dirty || email.touched)"
            class="alert alert-danger">
            <div *ngIf="email.errors.required">
              Email is required
            </div>
          </div>

          <div class="form-group">
            <label for="password">Password</label>
            <input type="password"
                   class="form-control"
                   [(ngModel)] = "formData.password"
                   #password = "ngModel"
                   name="password"
                   required>
          </div>
          <div *ngIf="password.invalid && (password.dirty || password.touched)"
            class="alert alert-danger">
            <div *ngIf="password.errors.required">
              Password is required
            </div>
          </div>

          <div class="form-group">
            <label for="passwordConfirmation">Confirm Password</label>
            <input type="password"
                   class="form-control"
                   [(ngModel)] = "formData.passwordConfirmation"
                   #passwordConfirmation = "ngModel"
                   name="passwordConfirmation"
                   required>
          </div>
           <div *ngIf="passwordConfirmation.invalid && (passwordConfirmation.dirty || passwordConfirmation.touched)"
            class="alert alert-danger">
            <div *ngIf="passwordConfirmation.errors.required">
              Confirm Password is required
            </div>
          </div>
          <button 
          (click) = "register()"
          type="submit" 
          class="btn btn-warning"
          [disabled] = "!registerForm.form.valid">Register</button>
        </form>
      </div>
    </div>
</div>

So, this is a template-based form in which we validate each field, and if the validation failed then, it would display a message. If all the validation rules pass, then and then, the register button will enable. Otherwise, it will disable.

On the Register button, we have put the click event. So if the user will click on the Register button, then register.component.ts file’s register() function will be called, and we can log the formData.

Here, we have bound the form’s data with ngModel. You can see that in every input field that

[(ngModel)] = “formData.fieldname“. So two-way data binding is possible with NgModel property.

That way, we can be able to log all the form’s data with this.formData.

Now, if all of your validation rules pass, then it will log each field’s data in the console.

 

User Registration in Angular 10

Step 17: Write Auth Service to register a user

To work with AJAX request or network request, we have to import the HttpClientModule inside the auth.module.ts file. So, let’s import that first.

// auth.module.ts

import { HttpClientModule } from '@angular/common/http';

imports: [
    RouterModule.forChild(routes),
    CommonModule,
    FormsModule,
    ReactiveFormsModule,
    HttpClientModule
  ],

Next, write the following code inside the auth.service.ts file.

// auth.service.ts

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';

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

  private uriseg = 'http://localhost:5000/api/users';

  constructor(private http: HttpClient) { }

  public register(userData: any): Observable<any> {
    const URI = this.uriseg + '/register';
    return this.http.post(URI, userData);
  }
}

In this file, we have imported the required modules and then defined a common URI, which will be the standard part of all the network requests URI.

Then we have defined register() function, which takes the formData as userData and sends the post request to the Node.js server and returns the Observable.

Now, come back to the register.component.ts file and add the following code.

// register.component.ts

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

@Component({
  selector: 'app-register',
  templateUrl: './register.component.html',
  styleUrls: ['./register.component.scss']
})
export class RegisterComponent implements OnInit {

  formData: any = {};
  errors: any = [];

  constructor(private auth: AuthService, private router: Router) { }

  ngOnInit(): void {
  }

  register(): void {
    this.errors = [];
    this.auth.register(this.formData)
      .subscribe(() => {
        this.router.navigate(['/auth/login'], { queryParams: { registered: 'success' } });
       },
        (errorResponse) => {
          this.errors.push(errorResponse.error.error);
        });
  }
}

So, this register() function subscribes to the Observable returned from the auth.service.ts file’s register() function.

If the user registration is successful, then we are navigating our application route to the Login page whose URI is /auth/login with message registered = success.

Now, if let’s say a user is already registered and try to register with the same email address again then, it won’t be able to do that, and it will display the error message saying that User already exists.

 

Failed user validation

So, here you can check the bunch of validation and see if it displays the proper message or not.

Now, if we submit the correct information, then it will redirect to the LoginComponent, and in the MongoDB database, you can see that new entry of the user is created.

 

MongoDB

So, that is for Angular 10 User Registration.

Step 18: Create and validate Login Form

We will use a ReactiveForms in LoginComponent.

Angular 10 Reactive Forms

Angular Reactive Forms or Model-Driven approach is another way to construct forms in Angular. In Angular Reactive Forms, the form is built in the component class. That allows you to have a contract that your form subscribes to field-value changes, and ultimately unit-test your form logic without any UI layer.

Write the following code inside the login.component.ts file.

// login.component.ts

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

@Component({
  selector: 'app-login',
  templateUrl: './login.component.html',
  styleUrls: ['./login.component.scss']
})
export class LoginComponent implements OnInit {

  loginForm: FormGroup;
  errors: any = [];
  notify: string;

  constructor(private auth: AuthService, private router: Router, private fb: FormBuilder, private route: ActivatedRoute) { }

  ngOnInit(): void {
    this.initForm();
    this.route.queryParams.subscribe((params) => {
      const key1 = 'registered';
      const key2 = 'loggedOut';
      if (params[key1] === 'success') {
        this.notify = 'You have been successfully registered. Please Log in';
      }
      if (params[key2] === 'success') {
        this.notify = 'You have been loggedout successfully';
      }
    });
  }

  initForm(): void {
    this.loginForm = this.fb.group({
      email: ['', [Validators.required,
      Validators.pattern('^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$')]],
      password: ['', Validators.required]
    });
  }

  isValidInput(fieldName): boolean {
    return this.loginForm.controls[fieldName].invalid &&
      (this.loginForm.controls[fieldName].dirty || this.loginForm.controls[fieldName].touched);
  }

  login(): void {
    console.log(this.loginForm.value);
  }
}

First, we have imported all the required modules for this component.

When the component is initializing, the ngOnInit() lifecycle method will be called. So in that method, we are initializing the form. So the intiForm() method will be called.

We are using FormBuilder and FormGroup modules to create and validate email and password fields.

Each form field will call the isValidInput() function, and if it fails to validate the specific form field, then it will display the validation message.

And finally, when the user clicks on the login button (ngSubmit) event will be fired, and if every field values are correct, then we are displaying the form values in the console.

Write the following code inside the login.component.html file.

<!-- login.component.html -->

<div class="container">
    <div class="row">
      <div class="col-md-5">
        <h1 class="page-title">Login</h1>
        <form [formGroup]="loginForm" (ngSubmit) = "login()">
          <div class="form-group">
            <div *ngIf="notify" class="alert alert-success">
              {{ notify }}
            </div>
            <div *ngIf="errors.length > 0" class="alert alert-danger">
              <p *ngFor = "let error of errors">
                  {{ error }}
              </p>
            </div>
            <label for="email">Email</label>
            <input type="email"
                   class="form-control"
                   formControlName="email">
          </div>
          <div *ngIf="isValidInput('email')"
            class="alert alert-danger">
            <div *ngIf="loginForm.controls['email'].errors.required">
              Email is required.
            </div>
            <div *ngIf="loginForm.controls['email'].errors.pattern">
              Must be a valid email format.
            </div>
          </div>

          <div class="form-group">
            <label for="password">Password</label>
            <input type="password"
                   class="form-control"
                   formControlName ="password">
          </div>
          <div *ngIf="isValidInput('password')"
            class="alert alert-danger">
            <div *ngIf="loginForm.controls['password'].errors.required">
              Password is required.
            </div>
          </div>
          <button
          [disabled]="!loginForm.valid"
          type="submit" 
          class="btn btn-warning"
          >Sign In</button>
        </form>
      </div>
    </div>
</div>

After you create a control in the component class, you must associate it with the form control element in the template. Update the template with a form control using the formControl binding provided by the FormControlDirective included in ReactiveFormsModule.

The FormControl class is the basic building block when using reactive forms. To register a single form control, import the FormControl class into your component, and create a new instance of the form control to save as a class property.

Just as a form control instance gives you control over a single input field, a form group instance tracks the form state of a group of form control instances (for example, a form). Each control in a form group instance is tracked by name when creating the form group.

To leverage a lifecycle event, you just need to add a known function name to your class, and Angular will handle calling it if it exists in your class.

Now, let’s check the validation of the Login form in the browser.

 

login form validation in Angular 10

Now, if we submit the correct values, then it will log in to the console.

 

Angular 10 Login Form

Step 19: Write Auth Service to log in a user

Now, first, we have to install one module called @auth0/angular-jwt module. It provides an API in Angular to work with the JWT token. If we have to decode it and extract the information, then it will be beneficial.

To do that, go to the terminal and type the following command.

npm install @auth0/angular-jwt --save

Now, our goal here is if the user successfully logged in, then we will get the JWT token from the node.js server. So we have to store this token in localStorage and also store decodedToken, which consists of userId and username and retrieve it whenever we need it.

Also, we have to check if the JWT token is expired or not. If expired, then we have to logout of our user and get back to the login page.

We will also need a moment library for time management. So let’s install it as well.

npm install moment --save

Now, write the following code inside the auth.service.ts file.

// auth.service.ts

import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { HttpClient } from '@angular/common/http';
import { JwtHelperService } from '@auth0/angular-jwt';
import * as moment from 'moment';

const jwt = new JwtHelperService();

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

  private uriseg = 'http://localhost:5000/api/users';
  private decodedToken;

  constructor(private http: HttpClient) { }

  public register(userData: any): Observable<any> {
    const URI = this.uriseg + '/register';
    return this.http.post(URI, userData);
  }

  public login(userData: any): Observable<any> {
    const URI = this.uriseg + '/login';
    return this.http.post(URI, userData).pipe(map(token => {
      return this.saveToken(token);
    }));
  }

  private saveToken(token: any): any {
    this.decodedToken = jwt.decodeToken(token);
    localStorage.setItem('auth_tkn', token);
    localStorage.setItem('auth_meta', JSON.stringify(this.decodedToken));
    return token;
  }
}

So, here we have defined two functions.

  1. login()
  2. saveToken()

The login function will post the user data to the server, and if it is successful, then it will return a token, and then we will pass that token to the saveToken() method.

It will first use the decode the token using decodeToken() function and then store the two items in the localStorage.

  1. auth_tkn: It is a JWT token.
  2. auth_meta: It is a string consist of userid and username. It is useful when we need to display the username of logged in user in the dashboard.

Now, go back to the login.component.ts file and write the following code.

// login.component.ts

login(): void {
    this.errors = [];
    this.auth.login(this.loginForm.value)
      .subscribe((token) => {
        this.router.navigate(['/'], { queryParams: { loggedin: 'success' } });
       },
        (errorResponse) => {
          this.errors.push(errorResponse.error.error);
        });
  }

Save the file and go to the browser and try to login with a valid username and password. You can see that we have logged in, and if you go to chrome dev tools and go to Application >> Local Storage, then you will see the two items. 

  1. auth_tkn
  2. auth_meta

The auth_tkn consists of JWT Token, and auth_meta consist of the following object.

{

    userId: “5f05ade9507cce3706a879cd”,

    username: “krunal”,

    iat: 1594267432,

    exp: 1594271032

}

Now, if you see in the navigation bar, then after logged in, we need to change the navigation items. Like if you are logged in, then there should be the nav items like Logout and Username, and if you are logged out, then there is an option called Register and Login.

So, let’s first create a logout functionality.

Step 20: How to logout in Angular

Now, here we have to write the logout function that logs out from the system.

To do that, we need to write a logout() function inside the auth.service.ts file.

So, go to the auth.service.ts file and write the logout() function.

// auth.service.ts

public logout(): void {
    localStorage.removeItem('auth_tkn');
    localStorage.removeItem('auth_meta');
}

On the client-side, we are just removing the items from local storage.

Now, we can initialize the empty decoded token by creating a class called DecodedToken inside the auth.service.ts file.

// auth.service.ts

class DecodedToken {
  exp: number;
  username: string;
}

And now, in the logout() function, we can write like this.

// auth.service.ts

public logout(): void {
    localStorage.removeItem('auth_tkn');
    localStorage.removeItem('auth_meta');

    this.decodedToken = new DecodedToken();
  }

So, our auth.service.ts file looks like below.

// auth.service.ts

import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { HttpClient } from '@angular/common/http';
import { JwtHelperService } from '@auth0/angular-jwt';
import * as moment from 'moment';

const jwt = new JwtHelperService();

class DecodedToken {
  exp: number;
  username: string;
}

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

  private uriseg = 'http://localhost:5000/api/users';
  private decodedToken;

  constructor(private http: HttpClient) { }

  public register(userData: any): Observable<any> {
    const URI = this.uriseg + '/register';
    return this.http.post(URI, userData);
  }

  public login(userData: any): Observable<any> {
    const URI = this.uriseg + '/login';
    return this.http.post(URI, userData).pipe(map(token => {
      return this.saveToken(token);
    }));
  }

  private saveToken(token: any): any {
    this.decodedToken = jwt.decodeToken(token);
    localStorage.setItem('auth_tkn', token);
    localStorage.setItem('auth_meta', JSON.stringify(this.decodedToken));
    return token;
  }

  public logout(): void {
    localStorage.removeItem('auth_tkn');
    localStorage.removeItem('auth_meta');

    this.decodedToken = new DecodedToken();
  }
}

Okay, now add this nav-item to the navigation bar. Means, as we have done with Register and Login, now there is one more link for log out.

Now, this logout link will be rendered conditionally because if the user is logged in then and then it will show the logout link; otherwise, it should not be showing this link.

So, we will create one another ng-container for the logout navigation link.

So, write the following code inside the header.component.html.

<ng-container>
    <li class="nav-item">
       <a class="nav-link pointer" (click)="logout()">
          Logout
       </a>
    </li>
</ng-container>

Now, we have yet to create a logout() function inside the header.component.ts file. So let’s do that.

Write the following code inside the header.component.ts file.

// header.component.ts

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

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

  constructor(public auth: AuthService, private router: Router) { }

  ngOnInit(): void {
  }

  logout(): void {
    this.auth.logout();
    this.router.navigate(['/auth/login'], {queryParams: {loggedOut: 'success'}});
  }
}

In this file, we have imported the auth.service.ts file and injected it as a dependency and use the logout() function to logging out from the auth system and navigate to the /auth/login route.

Also, write the following snippet inside the header.component.css file.

.pointer {
  cursor: pointer;
}

So, that’s it. Now, you will be able to logout from the system, and it will redirect to the login page.

Step 21: Show navigation links conditionally

Now, we will do the conditional rendering.

If the user is logged in, then we will display a logout link and username in the navigation bar; otherwise, we will show the login and register link.

First, we have to create a function called isauthenticated() inside the auth.service.ts file. The function will return true or false based on the token expiry. So, if the token is expired, then that means the user is logged out, then we don’t need to show a logout link, instead, we will display a login or register link.

So, write the following code inside the auth.service.ts file.

// auth.service.ts

public isAuthenticated(): boolean {
    return moment().isBefore(moment.unix(this.decodedToken.exp));
}

Also, you have to initialize the decodedToken with actual value or default value.

So, you have to initialize the decodedToken value inside the constructor of the auth.service.ts file.

// auth.service.ts

constructor(private http: HttpClient) {
    this.decodedToken = JSON.parse(localStorage.getItem('auth_meta')) || new DecodedToken();
}

So, up to now, the auth.service.ts file looks like this.

// auth.service.ts

import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { HttpClient } from '@angular/common/http';
import { JwtHelperService } from '@auth0/angular-jwt';
import * as moment from 'moment';

const jwt = new JwtHelperService();

class DecodedToken {
  exp: number;
  username: string;
}

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

  private uriseg = 'http://localhost:5000/api/users';
  private decodedToken;

  constructor(private http: HttpClient) {
    this.decodedToken = JSON.parse(localStorage.getItem('auth_meta')) || new DecodedToken();
   }

  public register(userData: any): Observable<any> {
    const URI = this.uriseg + '/register';
    return this.http.post(URI, userData);
  }

  public login(userData: any): Observable<any> {
    const URI = this.uriseg + '/login';
    return this.http.post(URI, userData).pipe(map(token => {
      return this.saveToken(token);
    }));
  }

  private saveToken(token: any): any {
    this.decodedToken = jwt.decodeToken(token);
    localStorage.setItem('auth_tkn', token);
    localStorage.setItem('auth_meta', JSON.stringify(this.decodedToken));
    return token;
  }

  public logout(): void {
    localStorage.removeItem('auth_tkn');
    localStorage.removeItem('auth_meta');

    this.decodedToken = new DecodedToken();
  }

  public isAuthenticated(): boolean {
    console.log(this.decodedToken.exp);
    return moment().isBefore(moment.unix(this.decodedToken.exp));
  }
}

And now, go to the header.component.html file and add the *ngIf condition.

<!-- header.component.html -->

<nav class="navbar navbar-light navbar-expand-lg" style="background-color: #e3f2fd;">
  <div class="container">
    <a routerLink="/" class="navbar-brand" routerLinkActive="active" >AppDividend</a>
    <div class="collapse navbar-collapse" id="navbarSupportedContent">
      <ul class="navbar-nav ml-auto">
        <ng-container *ngIf="!auth.isAuthenticated()">
          <li class="nav-item">
            <a routerLink="/auth/login" class="nav-link" routerLinkActive="active">
            Login
            </a>
          </li>
          <li class="nav-item">
            <a routerLink="/auth/register" class="nav-link">
            Register
            </a>
          </li>
         </ng-container>
         <ng-container *ngIf="auth.isAuthenticated()">
         <li class="nav-item">
            <a class="nav-link pointer" (click)="logout()">
            Logout
            </a>
          </li>
         </ng-container>
      </ul>
    </div>
  </div>
</nav>

So, if the user is logged in, then it will display the logout link; otherwise, it will display register and login links. So, that’s it for conditional rendering based on logged in user.

Step 22: Display the Logged In Username

To display the logged-in username, we have to create a method inside the auth.service.ts file.

// auth.service.ts

public getUsername(): string {
    return this.decodedToken.username;
}

The decodedToken has userId and username. So we can quickly get that.

Now, go to the header.component.html file and call the getUsername() function. 

Remember, if the user is logged in then and then we have to display its username.

<ng-container *ngIf="auth.isAuthenticated()">
     <li class="nav-item" >
         <a class="nav-link">
            {{ auth.getUsername() }}
          </a>
      </li>
      <li class="nav-item">
          <a class="nav-link pointer" (click)="logout()">
            Logout
          </a>
      </li>
</ng-container>

Now, if you are logged in, you can see the following page.

 

Logout in Angular

You can see that we have shown the username in the navigation bar.

Step 23: Protect private routes through AuthGuard

At the time of component creation, we have created an AuthGuard if you have remembered.

Now, we will write the code to protect the private routes from the public. That means, if the user is not logged in and tries to access a specific route, then it will redirect back to /auth/login.

Another scenario is that, if the user is logged in and tries to access the/auth/register or /auth/login page, then it will be redirected to the home page and can’t access these routes because he is already logged in.

Okay, so now write the following code inside the auth.guard.ts file.

// auth.guard.ts

import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { Router } from '@angular/router';

import { AuthService } from './auth.service';

@Injectable({
  providedIn: 'root'
})
export class AuthGuard implements CanActivate {
  private url: string;
  constructor(private auth: AuthService, private router: Router) { }

  private authState(): boolean {
    if (this.isLoginOrRegister()) {
      this.router.navigate(['/']);
      return false;
    }
    return true;
  }
  private notAuthState(): boolean {
    if (this.isLoginOrRegister()) {
      return true;
    }
    this.router.navigate(['/auth/login']);
    return false;
  }
  private isLoginOrRegister(): boolean {
    if (this.url.includes('/auth/login') || this.url.includes('/auth/register')) {
      return true;
    }
    return false;
  }

  canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): any {
    this.url = state.url;
    if (this.auth.isAuthenticated()) {
     return this.authState();
    }
    return this.notAuthState();
  }
}

So, basically what I have done is that first, it will check if the user is logged in or not using isAuthenticated() method.

If it is logged in, then it will return the authState() function. That function then will check if the user is trying to access the /auth/register or /auth/login page. If it does, then we will redirect to the home page; otherwise, it will continue.

If the user is not logged in, then it will return the noAuthState() function. In which, we will be redirected to /auth/login page.

So, this guard will act as middleware, and if we want to activate on a particular route, then we can do it where we have defined the routes.

Let’s activate the AuthGuard on different routes.

Write the following code inside the auth.module.ts file.

// auth.module.ts

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

const routes: Routes = [
  {
    path: 'auth',
    component: AuthComponent,
    children: [
      { path: 'login', component: LoginComponent, canActivate: [AuthGuard] },
      { path: 'register', component: RegisterComponent, canActivate: [AuthGuard] }
    ]
  }
];

In this code, you can see that we have passed a third option called canActivate, which takes AuthGuard.

You can do the same for the app-routing.module.ts file.

// app-routing.module.ts

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

import { ProfileComponent } from './profile/profile.component';
import { HomeComponent } from './home/home.component';

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

const routes: Routes = [
  { path: '', component: HomeComponent },
  { path: 'profile', component: ProfileComponent, canActivate: [AuthGuard] }
];

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

In this code, we have activated the guard on ProfileComponent.

So, if you are logged in and try to access the http://localhost:4200/profile, then you will be able to access it. Otherwise, you will be redirected to the login page.

So, that is it for the Angular JWT Authentication. 

Finally, our Angular 10 Tutorial is over, and I have put this whole code on Github.

GITHUB CODE

Steps to use Angular 10 Tutorial Github Code.

  1. Clone the Github repo. And go inside the project folder.
  2. Install all the angular dependencies by typing: npm install
  3. Go inside the auth folder and install all the node.js dependencies: npm install
  4. Start the MongoDB server by mongod command or whatever your preference, but it should start.
  5. To start node.js server, you have to go inside the auth folder and type: nodemon server
  6. To start Angular dev server, you have to go inside the root of angular folder and type: ng serve -o

Now, try to register a user and then log in, if every server is running fine then you will be able to do all the stuff II has described in this Angular 10 JWT Authentication Tutorial.

Conclusion

In this Angular 10 Tutorial, you have learned the following things.

  1. How to connect Angular 10 application to Node.js server.
  2. How to connect Node.js application to MongoDB Database.
  3. How to create user register and login functionality in MongoDB.
  4. How to generate a JWT token when the user successfully logged in.
  5. How to create middleware in Express application.
  6. How to create routes and subroutes in Angular 10.
  7. How to modularize the code in Angular.
  8. What are Template and Reactive Forms in Angular 10?
  9. How to use JWT Authentication in Angular.
  10. How to save JWT token in local storage.
  11. How to render client-side navigation based on user authentication.
  12. How to create AuthGuard and protect the private routes in Angular.

I know this Angular 10 tutorial is massive and somethings I have missed to explain.

But in my defense, the size of this tutorial is getting enormous. So please focus on the outcome and Github code. Thanks, and enjoy learning new things.

 

2 Comments
  1. Komal Khatkole says

    One of the best article I was wandering for.

    1. Krunal says

      Thanks. Keep sharing!!

Leave A Reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.