In this tutorial, We'll see how we can implement the modern JWT(JSON Web Token) authentication to our node and express application.
Here we'll create the three api endpoints: signup(That will register the new user), signin(That will validate the user credentials and returns the valid JWT), and account(That will validate the JWT and return the user information).
So, let's get started by creating a new node project.
mkdir jwt-auth
cd jwt-auth
npm init -y
npm install express mongoose jsonwebtoken dotenv bcryptjs
Create a new .env file at the root of the directory that will store the important environment settings of our project like DB Url, Secret keys, etc.
DB_URL=mongodb+srv://test:test887@cluster0.45871.mongodb.net/yes?retryWrites=true&w=majority
JWT_SECRET=mysupersecret
Now let's create the basic express server. The code here is simple and explained with the relevant comments.
const express = require("express");
const mongoose = require("mongoose");
// Load the environment variables
require("dotenv").config();
// Create the express app
const app = express();
// Connect with the mongo db
try{
mongoose.connect(process.env.DB_URL,
{ useNewUrlParser: true ,useUnifiedTopology: true},
()=>{console.log("DB Connected")})
}catch(err){
console.log(err)
}
// Parse the request body
app.use(express.json());
app.use(express.urlencoded({
extended: true
}));
const PORT = 8000 || process.env.PORT
app.listen(PORT, ()=>console.log(`Listening at port ${PORT}`))
Here I am creating the main user model with only three string fields(email, name, password).
const mongoose = require('mongoose')
const userSchema = mongoose.Schema({
name: String,
email: String,
password: String
})
module.exports = mongoose.model('User' ,userSchema)
Let's create the authentication controller with three functions.
const jwt = require("jsonwebtoken");
const bcrypt = require("bcrypt");
const User = require("../models/User")
module.exports = {
// Register the user
signup: async (req, res)=>{
},
// Login the user
signin: async (req, res)=>{
},
// User account details
me: async (req, res)=>{
},
}
This function will be responsible for the sign-up of the user. Here we first check if the user provided the right inputs or not. Then we check if the user of a given email already exists or not. Finally, we encrypt the password and register the new user.
// Register the user
signup: async (req, res)=>{
// validate the user input
if(!(req.body.name && req.body.email && req.body.password)){
return res.status(400).json({
okay: false,
msg: "Incomplete input data",
data: {}
})
}
// Check if the user with the given email exists or not
let userExists = await User.findOne({email})
if(userExists){
return res.status(400).json({
okay: false,
msg: "User with this email already exists",
data: {}
})
}
// Encrypt the user password
let encryptedPassword = await bcrypt.hash(req.body.password, 16)
// Create the new user
let newUser = await User.create({
email: req.body.email,
name: req.body.name,
password: encryptedPassword
})
res.json({
okay: true,
msg: "User created successfully",
data: {
email: newUser.email,
name: newUser.name
}
})
},
In this function, we first validate the user input. Then check if the credentials are valid or not. Finally, sign and return the JWT token to the user.
// Login the user
signin: async (req, res)=>{
// Validate the user input
if(!(req.body.email && req.body.password)){
return res.status(403).json({
okay: false,
msg: "Incomplete input data",
data: {}
})
}
// Try to fetch the user from db with the given email
let user = await User.findOne({email: req.body.email})
// Check if user exists and password matches
if(user && (await bcrypt.compare(req.body.password, user.password))){
// Create the token
let token = jwt.sign({
id_: user._id,
email: user.email,
name: user.name
}, process.env.JWT_SECRET)
return res.json({
okay: true,
msg: "Login success",
data: {token}
})
}
return res.status(400).json({
okay: false,
msg: "Invalid email or password",
data: {}
})
},
Here we simply return the user account details.
// User account details
me: async (req, res)=>{
// req.user is coming from the login_required middleware(Discussed next)
let user = req.user
res.json({
okay: true,
msg: "Details fetched successfully",
data: {user}
})
},
Let's create a middleware whose purpose will be to check and validate the JWT token from the request headers and return 403 if the token is invalid else call the next function.
const jwt = require("jsonwebtoken");
module.exports = async (req, res, next) =>{
// Get the token from the Authorization header
let token = req.headers['authorization']
// if token not present then return the 403
if(!token){
return res.status(403).json({
okay: false,
msg: "Token is required",
data: {}
})
}
// Split the 'Bearer <Token>' and get the token portion
token = token.split(" ")[1]
// Check if the token is valid or not
try{
let decoded = await jwt.verify(token, process.env.JWT_SECRET)
req.user = decoded
}catch(err){
// If token is invalid then send the 401
return res.status(401).json({
okay: false,
msg: "Invalid JWT token",
data: {}
})
}
return next()
}
Let's create the routes for our authentication api.
const router = require("express").Router();
const controller = require("../controllers/authController");
const loginRequired = require("../middlewares/loginRequired")
// Sign up route
router.post("/signup", controller.signup)
// Sign in route
router.post("/signin", controller.signin)
// Account Details
router.post("/me", loginRequired, controller.me)
module.exports = router;
Note: we added the loginRequired middleware to the account details route.
Also, let's add our routes to the server.
// Rest of the code is same
// Auth routes
app.use('/api/auth', require("./routes/authRoutes"))
const PORT = 8000 || process.env.PORT
app.listen(PORT, ()=>console.log(`Listening at port ${PORT}`))
Finally, let's run the node server and test the api.
node app.js