Welcome folks today in this blog post we will be building a jwt
authentication system in angular and node.js
and express using mongodb in browser. All the full source code of the application is shown below.
BUY FULL SOURCE CODE
Get Started
In order to get started you need to make a new directory called nodeangularauth
and inside it we will be making the angular
frontend and node.js
backend as well
mkdir nodeangularauth
And after that we will be first of all creating the node.js
backend api for the authentication system as shown below
Making the Backend
Now we need to make the backend
directory as shown below
mkdir backend
npm init -y
npm i express
npm i cors
npm i mongoose
npm i cookie-parser
npm i bcryptjs
npm i jsonwebtoken
Express
: This will be the web server which we will be using for our application
mongoose
: It will be dependency which we will be using for interacting with the Mongodb
database
bcryptjs
: This will be used for hashing
the passwords
jsonwebtoken
This library will generate and verify the json
web token and let users login into the protected routes.
cookie-parser
: This dependency will allow users to create and read cookies inside the express application
Directory Structure
At the end of this app this will be the directory
structure of the backend as shown below
Now we need to make the index.js
file and copy paste the following code
index.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
const express = require('express') const mongoose = require('mongoose') const cors = require('cors') const cookieParser = require('cookie-parser') mongoose.connect('mongodb://localhost/node_auth', { useNewUrlParser: true, useUnifiedTopology: true }, () => { console.log('connected to the database') }) const routes = require('./routes/routes') app = express() app.use(cookieParser()) app.use(cors({ credentials: true, origin: ['http://localhost:4200'] })) app.use(express.json()) app.use('/api', routes) app.listen(8000) |
As you can see we are starting the express
app at the port 8000 and then we are also passing the middleware
of the cookie parser so that we can allow to read
and set
the cookies. And then we are also importing the routes
file and also passing it as the middleware. And then we are also passing the cors
middleware and here we are passing the origin
where we allow the requests from that particular origin only. And also we are importing the mongoose
library at the top and then we are connecting it to the database
Making the Models
Now we need to make the models
folder and there we need to make the user.js
file and copy paste the following code
models/user.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
const mongoose = require('mongoose') const userSchema = new mongoose.Schema({ name: { type: String, required: true }, email: { type: String, unique: true, required: true }, password: { type: String, required: true } }) module.exports = mongoose.model('User', userSchema) |
As you can see we are importing the mongoose library at the top and then we are defining the schema
of the collection and there we have three fields such as name
email and password and then we are creating the model
with the specified schema as shown above.
Making the Routes
Now we will be making the routes
for defining all the auth
operations such as the login, register and logout functions as shown below
routes/routes.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 |
const router = require("express").Router(); const bcrypt = require("bcryptjs"); const jwt = require("jsonwebtoken"); const User = require("../models/user"); router.post("/register", async (req, res) => { const salt = await bcrypt.genSalt(10); const hashedPassword = await bcrypt.hash(req.body.password, salt); const record = await User.findOne({ email: req.body.email }); if (record) { return res.status(400).send({ message: "Email is already registered", }); } else { const user = new User({ name: req.body.name, email: req.body.email, password: hashedPassword, }); const result = await user.save(); const { _id } = await result.toJSON(); const token = jwt.sign({ _id: _id }, "secret"); res.cookie("jwt", token, { httpOnly: true, maxAge: 24 * 60 * 60 * 1000, // 1 day }); res.send({ message: "success", }); } }); router.post("/login", async (req, res) => { const user = await User.findOne({ email: req.body.email }); if (!user) { return res.status(404).send({ message: "User not Found", }); } if (!(await bcrypt.compare(req.body.password, user.password))) { return res.status(400).send({ message: "Password is Incorrect", }); } const token = jwt.sign({ _id: user._id }, "secret"); res.cookie("jwt", token, { httpOnly: true, maxAge: 24 * 60 * 60 * 1000, // 1 day }); res.send({ message: "success", }); }); router.get("/user", async (req, res) => { try { const cookie = req.cookies["jwt"]; const claims = jwt.verify(cookie, "secret"); if (!claims) { return res.status(401).send({ message: "unauthenticated", }); } const user = await User.findOne({ _id: claims._id }); const { password, ...data } = await user.toJSON(); res.send(data); } catch (e) { return res.status(401).send({ message: "unauthenticated", }); } }); router.post("/logout", (req, res) => { res.cookie("jwt", "", { maxAge: 0 }); res.send({ message: "success", }); }); module.exports = router; |
As you can see we are importing all the dependencies
at the top and the inside the register and login functions we are taking the email
and password and then inserting it inside the database and also we are checking if already the email exists or not. And also if password match or not in case of login
and then we are sending the error
messages back to the client as well. And also we are setting the jwt
token inside the cookie using the sign()
method with the secret
key and also we are verifying the jwt
token using the verify()
method and lastly for the logout method we are clearing
out the cookie data.
Making the Angular Frontend
Now we need to make the frontend
directory and inside it we need to make a new angular
project using the below command as shown below
mkdir frontend
ng new jwtfrontend
cd jwtfrontend
Now we will be installing the dependencies
which will be needed for this angular project as shown below
npm i sweetalert2
This is the only library
we need to in order to display popup messages to the user when they enter
invalid data
Now you will see the below directory
structure at the end of the angular
project as shown below
Creating the Components
As you can see in the above directory screenshot we need to create the four
components for our angular frontend namely the home
component where we will be displaying the user
info and then we have the login
and register
forms component and lastly we will be having the navigation
component where we will displaying all the buttons which will be common inside every component.
ng generate component home
ng generate component nav
ng generate component login
ng component generate register
Making the Routes
Now guys we need to edit the app-routing.module.ts
file and copy paste the following code
app-routing.module.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
import {NgModule} from '@angular/core'; import {Routes, RouterModule} from '@angular/router'; import {HomeComponent} from './home/home.component'; import {LoginComponent} from './login/login.component'; import {RegisterComponent} from './register/register.component'; const routes: Routes = [ {path: '', component: HomeComponent}, {path: 'login', component: LoginComponent}, {path: 'register', component: RegisterComponent}, ]; @NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule] }) export class AppRoutingModule { } |
As you can see we are registering all the components
that we created earlier with their respective
paths inside the routes variable. Now the routing of our angular project is properly configured for example if user
goes to the /
path then he or she will be redirected to the HomeComponent
it will be rendered.
Adding the Forms & HttpClient Module
Now for this angular frontend we will be using the html5
forms and also we will be making the http
requests to the backend. For both these tasks we need to include the formsModule
and the httpClientModule
inside the app.module.ts
file of your angular project.
app.module.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
import {BrowserModule} from '@angular/platform-browser'; import {NgModule} from '@angular/core'; import {AppRoutingModule} from './app-routing.module'; import {AppComponent} from './app.component'; import {RegisterComponent} from './register/register.component'; import {LoginComponent} from './login/login.component'; import {HomeComponent} from './home/home.component'; import {NavComponent} from './nav/nav.component'; import {FormsModule, ReactiveFormsModule} from '@angular/forms'; import {HttpClientModule} from '@angular/common/http'; @NgModule({ declarations: [ AppComponent, RegisterComponent, LoginComponent, HomeComponent, NavComponent ], imports: [ BrowserModule, AppRoutingModule, FormsModule, ReactiveFormsModule, HttpClientModule ], providers: [], bootstrap: [AppComponent] }) export class AppModule { } |
Styling the App
Now guys for styling the app we need to add the bootstrap
cdn inside the index.html
file of your angular project
index.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <title>Angular14frontend</title> <base href="/"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="icon" type="image/x-icon" href="favicon.ico"> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-giJF6kkoqNQ00vy+HMDP7azOuL0xtbfIcaT9wjKHr8RbDVddVHyTfAAsrekwKmP1" crossorigin="anonymous"> </head> <body> <app-root></app-root> </body> </html> |
And then inside the app.component.css
file we need to copy paste the below custom
css as shown below
app.component.css
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
.form-signin { width: 100%; max-width: 330px; padding: 15px; margin: auto; } .form-signin .checkbox { font-weight: 400; } .form-signin .form-control { position: relative; box-sizing: border-box; height: auto; padding: 10px; font-size: 16px; } .form-signin .form-control:focus { z-index: 2; } .form-signin input[type="email"] { margin-bottom: -1px; border-bottom-right-radius: 0; border-bottom-left-radius: 0; } .form-signin input[type="password"] { margin-bottom: 10px; border-top-left-radius: 0; border-top-right-radius: 0; } |
And now inside the app.component.html
file we need to include the below html
code as shown below
app.component.html
1 2 3 4 5 |
<app-nav></app-nav> <main class="form-signin"> <router-outlet></router-outlet> </main> |
As you can see the nav
component we included at the top is common in all the pages and after that we have embedded
the router and based upon which path
is hit different components will be loaded.
And lastly guys you need to add the below line inside the tsconfig.json
file to make this property
false inside the compiler options as shown below
tsconfig.json
1 2 3 4 5 |
"compilerOptions": { "strictPropertyInitialization": false } |
Making the Navbar
Now guys we will be making the navbar
component in which we will be displaying the login
and register buttons for now as shown below
nav.component.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<nav class="navbar navbar-expand-md navbar-dark bg-dark mb-4"> <div class="container-fluid"> <a routerLink="/" class="navbar-brand">Home</a> <div> <ul class="navbar-nav me-auto mb-2 mb-md-0"> <li class="nav-item"> <a routerLink="/login" class="nav-link">Login</a> </li> <li class="nav-item"> <a routerLink="/register" class="nav-link">Register</a> </li> </ul> </div> </div> </nav> |
Registering Users
Now guys we will be making the register
form and allowing the user to enter the name
email and the password as shown below
register.component.html
1 2 3 4 5 6 7 8 9 10 11 |
<form [formGroup]="form" (submit)="submit()"> <h1 class="h3 mb-3 fw-normal">Please register</h1> <input formControlName="name" class="form-control" placeholder="Name" required> <input formControlName="email" type="email" class="form-control" placeholder="Email" required> <input formControlName="password" type="password" class="form-control" placeholder="Password" required> <button class="w-100 btn btn-lg btn-primary" type="submit">Submit</button> </form> |
As you can see we have binded the submit
directive to the form and we are executing the submit()
method once we click the register
button in the form. And now we need to define this method inside the register.component.ts
file and copy paste the following code
register.component.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
import { Component, OnInit } from '@angular/core'; import { FormBuilder, FormGroup } from '@angular/forms'; import { HttpClient } from '@angular/common/http'; import { Router } from '@angular/router'; import Swal from 'sweetalert2'; @Component({ selector: 'app-register', templateUrl: './register.component.html', styleUrls: ['./register.component.css'], }) export class RegisterComponent implements OnInit { form: FormGroup; constructor( private formBuilder: FormBuilder, private http: HttpClient, private router: Router ) {} ngOnInit(): void { this.form = this.formBuilder.group({ name: '', email: '', password: '', }); } ValidateEmail = (email: any) => { var validRegex = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/; if (email.match(validRegex)) { return true; } else { return false; } } submit(): void { let user = this.form.getRawValue(); if (user.name == "" || user.email == '' || user.password == '') { Swal.fire('Error', 'Please enter all the fields', 'error'); }else if(!this.ValidateEmail(user.email)){ Swal.fire('Error', 'Please enter a valid email address', 'error'); } else { this.http .post('http://localhost:8000/api/register', this.form.getRawValue(), { withCredentials: true, }) .subscribe(() => this.router.navigate(['/']),(err) => { Swal.fire("Error",err.error.message,'error') }) } } } |
As you can see we are getting the values
submitted by the user and first of all checking if the values are not empty and also the email is valid
or not and we are showing error
messages with the help of sweetAlert2 library as well. And lastly if details are correct we are making the post
request with the help of httpClient
to the backend and their we are passing the data and then we are also passing the withCredentials
option which automatically saves the jwt
token to the cookies and pass it to the backend. And then inside the subscribe()
callback we get the data and here we are redirecting the user to the home
page. And if any error takes place we are showing the error message to the user.
If details are correct then the user will be saved inside the mongodb
database as shown below
Showing User Details
Now guys we need to write the code inside the home
component where we will be redirected
once we are successfully registered
or logged
in. And for this you need to copy paste the following code in home.component.ts
file
home.component.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
import { Component, OnInit } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { Emitters } from '../emitters/emitters'; @Component({ selector: 'app-home', templateUrl: './home.component.html', styleUrls: ['./home.component.css'], }) export class HomeComponent implements OnInit { message = ''; constructor(private http: HttpClient) {} ngOnInit(): void { this.http .get('http://localhost:8000/api/user', { withCredentials: true }) .subscribe( (res: any) => { this.message = `Hi ${res.name}`; Emitters.authEmitter.emit(true); }, (err) => { this.message = 'You are not logged in'; Emitters.authEmitter.emit(false); } ); } } |
As you can see we are using the ngOnInit()
lifecycle method and inside it we are making the get
request to the backend to get the user and we are passing the jwt
token which is generated in the cookies with the help of withCredentials
option set to true in the options. And now inside the callback function we are creating the dynamic
message hi followed by the name of the user. And for this we need to communicate to the nav
component. For this we need to use the emitters
class of the angular core library. For this you need to create a folder called emitters
and inside it we need to define the emitter.ts
file and copy paste the below code
emitters/emitter.ts
1 2 3 4 5 |
import {EventEmitter} from '@angular/core'; export class Emitters { static authEmitter = new EventEmitter<boolean>(); } |
Now we are using this emitter in the above code to push the value
of the authenticated user it will be boolean either true or false depending on the state of the user. Now any component can subscribe to this emitter
and get the value. Now we will be going to the nav.component.ts
file and copy paste the following code
nav.component.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
import {Component, OnInit} from '@angular/core'; import {Emitters} from '../emitters/emitters'; import {HttpClient} from '@angular/common/http'; @Component({ selector: 'app-nav', templateUrl: './nav.component.html', styleUrls: ['./nav.component.css'] }) export class NavComponent implements OnInit { authenticated = false; constructor(private http: HttpClient) { } ngOnInit(): void { Emitters.authEmitter.subscribe( (auth: boolean) => { this.authenticated = auth; } ); } logout(): void { this.http.post('http://localhost:8000/api/logout', {}, {withCredentials: true}) .subscribe(() => this.authenticated = false); } } |
As you can see we are importing the emitter
created by the home
component and then we are getting the value of it by subscribing to it and depending on it’s value we are showing the logout
button only if the user is authenticated in the html. And also we have defined the logout
method where we are simply making the authenticated
value to false and hiding the logout button and showing the login and register button.
And now inside the nav.component.html
file we need make slight modifications as shown below
nav.component.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
<nav class="navbar navbar-expand-md navbar-dark bg-dark mb-4"> <div class="container-fluid"> <a routerLink="/" class="navbar-brand">Home</a> <div> <ul class="navbar-nav me-auto mb-2 mb-md-0" *ngIf="!authenticated"> <li class="nav-item"> <a routerLink="/login" class="nav-link">Login</a> </li> <li class="nav-item"> <a routerLink="/register" class="nav-link">Register</a> </li> </ul> <ul class="navbar-nav me-auto mb-2 mb-md-0" *ngIf="authenticated"> <li class="nav-item"> <a routerLink="/login" class="nav-link" (click)="logout()">Logout</a> </li> </ul> </div> </div> </nav> |
We have added a slight
ngIf condition where we have passed the value of the authenticated
boolean variable.
Adding the Login Form
Now guys we will be adding the code for displaying the login
form so go to login.component.html
file and copy paste the below html code
login.component.html
1 2 3 4 5 6 7 8 9 |
<form [formGroup]="form" (submit)="submit()"> <h1 class="h3 mb-3 fw-normal">Please sign in</h1> <input formControlName="email" type="email" class="form-control" placeholder="Email" required> <input formControlName="password" type="password" class="form-control" placeholder="Password" required> <button class="w-100 btn btn-lg btn-primary" type="submit">Sign in</button> </form> |
As you can see we have two fields to enter the email
and password
and we have attached the event handler when we submit the form. Now we need to define this function inside the login.component.ts
file as shown below
login.component.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
import { Component, OnInit } from '@angular/core'; import { FormBuilder, FormGroup } from '@angular/forms'; import { HttpClient } from '@angular/common/http'; import { Router } from '@angular/router'; import Swal from 'sweetalert2'; @Component({ selector: 'app-login', templateUrl: './login.component.html', styleUrls: ['./login.component.css'], }) export class LoginComponent implements OnInit { form: FormGroup; constructor( private formBuilder: FormBuilder, private http: HttpClient, private router: Router ) {} ngOnInit(): void { this.form = this.formBuilder.group({ email: '', password: '', }); } ValidateEmail = (email: any) => { var validRegex = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/; if (email.match(validRegex)) { return true; } else { return false; } } submit(): void { let user = this.form.getRawValue(); if (user.email == '' || user.password == '') { Swal.fire('Error', 'Please enter all the fields', 'error'); }else if(!this.ValidateEmail(user.email)){ Swal.fire('Error', 'Please enter a valid email address', 'error'); } else { this.http .post('http://localhost:8000/api/login', user, { withCredentials: true, }) .subscribe( (res) => this.router.navigate(['/']), (err) => { Swal.fire('Error', err.error.message, 'error'); } ); } } } |
As you can see we are once again getting the values
submitted by the user validating it and showing the error messages
using the sweetlaert2 library and then making a post
request to the backend api passing the user info and then we are redirecting the user
to the home page
.
If you see your browser
cookies by inspect element you will see the jwt
token created as shown below
BUY FULL SOURCE CODE