DEV Community

Cover image for Authentication in Node.js with MongoDB, bcrypt, and JWT web Tokens with cookies 🍪.
Ritesh Kumar
Ritesh Kumar

Posted on • Updated on

Authentication in Node.js with MongoDB, bcrypt, and JWT web Tokens with cookies 🍪.

Adding authentication to an application is one of the most challenging 😖 but also a very important part for developers, but today I will teach you 🥰 how to do it, come on let's make an authentication page with me today in just 10 minutes ⚡.

1.Let's initialize npm and install all the necessary packages that we are going to use.

npm init -y
npm i express bcryptjs body-parser dotenv ejs jsonwebtoken mongoose cookie-parser
Enter fullscreen mode Exit fullscreen mode

2.Now create 2 directories views and public and also create server.js file now your folder structure should look like this 👇.

image

3.Now include the packages in your server.js and create an express server

here we included all the packages and required code to configure our express server that we will need throughout the journey in this article 🤠.

const express = require('express');
const bodyparser=require("body-parser");
const mongoose= require('mongoose');
const jwt = require('jsonwebtoken');
var cookieParser = require('cookie-parser');
const port = process.env.PORT || 3000;
const app = express();
require('dotenv').config();
const bcrypt = require('bcryptjs');
const salt = 10;
app.set('view engine', 'ejs');
app.use(bodyparser.urlencoded({extended:true}));
app.use(express.json());
app.use(cookieParser());
app.use(express.static("public"));
app.listen(port,()=>{
    console.log(`Running on port ${port}`);
})
Enter fullscreen mode Exit fullscreen mode

4.Now Create 3 files in the views folder 👇.

image

5.Now lets create our login signup and the protected page.

// signin.ejs
<form action="/login" method="post">
<label for="">Email</label>
<input type="email" name="email" id="">
<label for="">Password</label>
<input type="text" name="password" id="">
<button type="submit">SignIN</button>
</form>
<form action="/signup" method="get">
<button type="submit">
    Do not have an account
</button>
</form>
Enter fullscreen mode Exit fullscreen mode
// signup.ejs
<form action="/signup" method="post">
<label for="">Email</label>
<input type="email" name="email" id="">
<label for="">Password</label>
<input type="text" name="password" id="">
<button type="submit">SignUP</button>
</form>
Enter fullscreen mode Exit fullscreen mode
//home.ejs
This is the protected page
Enter fullscreen mode Exit fullscreen mode

6.Now we will create our .env file and store our secret key of JWT and mongodb connection url and add to our server.

image

// get our urls and secrets
const JWT_SECRET=process.env.jwt;
const MONGODB_URL=process.env.mongodb;

// making connnection with our database
mongoose.connect(MONGODB_URL, {useFindAndModify: false,useNewUrlParser: true, useUnifiedTopology: true,useCreateIndex: true});
Enter fullscreen mode Exit fullscreen mode

Now your server should look like this 👇.

const express = require('express');
const bodyparser=require("body-parser");
const mongoose= require('mongoose');
const jwt = require('jsonwebtoken');
var cookieParser = require('cookie-parser');
const port = process.env.PORT || 3000;
const app = express();
require('dotenv').config();
const bcrypt = require('bcryptjs');
const salt = 10;
app.set('view engine', 'ejs');
app.use(bodyparser.urlencoded({extended:true}));
app.use(express.json());
app.use(cookieParser());
app.use(express.static("public"));

// get our urls and secrets
const JWT_SECRET=process.env.jwt;
const MONGODB_URL=process.env.mongodb;

// making connnection with our database
mongoose.connect(MONGODB_URL, {useFindAndModify: false,useNewUrlParser: true, useUnifiedTopology: true,useCreateIndex: true});



app.listen(port,()=>{
    console.log(`Running on port ${port}`);
})
Enter fullscreen mode Exit fullscreen mode

7.Now we will create Our Schema for User Authentication and our signup method.

// Schema For User Auth
const userSchema = new mongoose.Schema({
    email:{type:String,required:true,unique:true},
    password:{type:String,required:true}
},{collection:'users'}
const User= mongoose.model("User",userSchema);
)
Enter fullscreen mode Exit fullscreen mode
app.post('/signup',async (req,res)=>{
    // geting our data from frontend
    const {email,password:plainTextPassword}=req.body;
    // encrypting our password to store in database
    const password = await bcrypt.hash(plainTextPassword,salt);
    try {
        // storing our user data into database
        const response = await User.create({
            email,
            password
        })
        return res.redirect('/');
    } catch (error) {
        console.log(JSON.stringify(error));
        if(error.code === 11000){
            return res.send({status:'error',error:'email already exists'})
        }
        throw error
    }
})
Enter fullscreen mode Exit fullscreen mode

8.Now we will create our Login method here we will use JWT to create an auth token and store it in our browser as a cookie

// user login function
const verifyUserLogin = async (email,password)=>{
    try {
        const user = await User.findOne({email}).lean()
        if(!user){
            return {status:'error',error:'user not found'}
        }
        if(await bcrypt.compare(password,user.password)){
            // creating a JWT token
            token = jwt.sign({id:user._id,username:user.email,type:'user'},JWT_SECRET,{ expiresIn: '2h'})
            return {status:'ok',data:token}
        }
        return {status:'error',error:'invalid password'}
    } catch (error) {
        console.log(error);
        return {status:'error',error:'timed out'}
    }
}

// login 
app.post('/login',async(req,res)=>{
    const {email,password}=req.body;
    // we made a function to verify our user login
    const response = await verifyUserLogin(email,password);
    if(response.status==='ok'){
        // storing our JWT web token as a cookie in our browser
        res.cookie('token',token,{ maxAge: 2 * 60 * 60 * 1000, httpOnly: true });  // maxAge: 2 hours
        res.redirect('/');
    }else{
        res.json(response);
    }
})
Enter fullscreen mode Exit fullscreen mode

9.And finally we will make routes for our remaining pages and check for auth for getting into our protected page

const verifyToken = (token)=>{
    try {
        const verify = jwt.verify(token,JWT_SECRET);
        if(verify.type==='user'){return true;}
        else{return false};
    } catch (error) {
        console.log(JSON.stringify(error),"error");
        return false;
    }
}



// get requests
app.get('/',(req,res)=>{
    const {token}=req.cookies;
    if(verifyToken(token)){
        return res.render('home');
    }else{
        res.redirect('/login')
    }
})

app.get('/login',(req,res)=>{
    res.render('signin');
})

app.get('/signup',(req,res)=>{
    res.render('signup')
})


app.listen(port,()=>{
    console.log(`Running on port ${port}`);
})
Enter fullscreen mode Exit fullscreen mode

10.Finally Your server.js should look like this 👇.

const express = require('express');
const bodyparser=require("body-parser");
const mongoose= require('mongoose');
const jwt = require('jsonwebtoken');
var cookieParser = require('cookie-parser');
const port = process.env.PORT || 3000;
const app = express();
require('dotenv').config();
const bcrypt = require('bcryptjs');
const salt = 10;
app.set('view engine', 'ejs');
app.use(bodyparser.urlencoded({extended:true}));
app.use(express.json());
app.use(cookieParser());
app.use(express.static("public"));

// get our urls and secrets
const JWT_SECRET=process.env.jwt;
const MONGODB_URL=process.env.mongodb;

// making connnection with our database
mongoose.connect(MONGODB_URL, {useFindAndModify: false,useNewUrlParser: true, useUnifiedTopology: true,useCreateIndex: true});

// Schema For User Auth
const userSchema = new mongoose.Schema({
    email:{type:String,required:true,unique:true},
    password:{type:String,required:true}
},{collection:'users'}
)
const User= mongoose.model("User",userSchema);

app.post('/signup',async (req,res)=>{
    // geting our data from frontend
    const {email,password:plainTextPassword}=req.body;
    // encrypting our password to store in database
    const password = await bcrypt.hash(plainTextPassword,salt);
    try {
        // storing our user data into database
        const response = await User.create({
            email,
            password
        })
        return res.redirect('/');
    } catch (error) {
        console.log(JSON.stringify(error));
        if(error.code === 11000){
            return res.send({status:'error',error:'email already exists'})
        }
        throw error
    }
})


// user login function
const verifyUserLogin = async (email,password)=>{
    try {
        const user = await User.findOne({email}).lean()
        if(!user){
            return {status:'error',error:'user not found'}
        }
        if(await bcrypt.compare(password,user.password)){
            // creating a JWT token
            token = jwt.sign({id:user._id,username:user.email,type:'user'},JWT_SECRET,{ expiresIn: '2h'})
            return {status:'ok',data:token}
        }
        return {status:'error',error:'invalid password'}
    } catch (error) {
        console.log(error);
        return {status:'error',error:'timed out'}
    }
}

// login 
app.post('/login',async(req,res)=>{
    const {email,password}=req.body;
    // we made a function to verify our user login
    const response = await verifyUserLogin(email,password);
    if(response.status==='ok'){
        // storing our JWT web token as a cookie in our browser
        res.cookie('token',token,{ maxAge: 2 * 60 * 60 * 1000, httpOnly: true });  // maxAge: 2 hours
        res.redirect('/');
    }else{
        res.json(response);
    }
})

const verifyToken = (token)=>{
    try {
        const verify = jwt.verify(token,JWT_SECRET);
        if(verify.type==='user'){return true;}
        else{return false};
    } catch (error) {
        console.log(JSON.stringify(error),"error");
        return false;
    }
}



// get requests
app.get('/',(req,res)=>{
    const {token}=req.cookies;
    if(verifyToken(token)){
        return res.render('home');
    }else{
        res.redirect('/login')
    }
})

app.get('/login',(req,res)=>{
    res.render('signin');
})

app.get('/signup',(req,res)=>{
    res.render('signup')
})


app.listen(port,()=>{
    console.log(`Running on port ${port}`);
})
Enter fullscreen mode Exit fullscreen mode

Hurray!! You have successfully added authentication in your website 🥳🥳🥳🥳.

Connect me on Twitter :-
https://twitter.com/nyctonio

Do check out my Github for source code :-https://github.com/nyctonio/jwtauth

Top comments (29)

Collapse
 
charlyjazz profile image
Carlos Azuaje

JWT For session is a bad design.

Collapse
 
maswerdna profile image
Samson Andrew

Don't do that. It's discouraging!

Next time, try to say kudos for the write-up, then suggest something better 🙂

Collapse
 
nyctonio profile image
Ritesh Kumar

🥺 thank you

Collapse
 
nyctonio profile image
Ritesh Kumar • Edited

No this is not a session based authorization it is a token based but what I did is stored that token in the cookie rather than sending them through the Authorization header for every HTTP request we can do in that way too but I thought that cookie one will be easier to understand for beginners.

Collapse
 
drsimplegraffiti profile image
Abayomi Ogunnusi

Nice post 👍👍...
I tend to use Express: app.use(express.json())

app.use(bodyparser.json()) outputs depreciation warnings here...just my preference though

Collapse
 
maswerdna profile image
Samson Andrew

You're right.
The recommended way is to call the methods directly on the express object.

Because...
In the latest version of Express, some bodyParser's methods have been added natively. That's why you don't have to install or require bodyParser again.

Collapse
 
nyctonio profile image
Ritesh Kumar

Yes you are right from now i will also use the same thanks for pointing it out 🤍

Collapse
 
maxinejs profile image
Maxine

@icecoffee is this the answer ?!

Collapse
 
icecoffee profile image
Atulit Anand • Edited

Here the author is talking about server side authentication.

Collapse
 
artis3n profile image
Ari Kalfus

Please take a look at auth0.com/blog/adding-salt-to-hash... to understand how to properly set a salt. A global value for your application is Not Good. The value does not need to be a secret, but it needs to be unique for every record you are hashing (unique per-password/user/record).

You typically store the salt with the hash either in a row in the same DB table or even prepended/appended to the hash with a delimeter. All that matters is the salt is unique per input.

From the Auth0 article:

A system-wide salt is pointless to mitigate attacks; it would just make passwords longer. A system-wide salt also easily allows an attacker to keep using hash tables. We should hash and salt each password created for a user. That is, we should generate a unique salt upon creation of each stored credential (not just per user or system-wide). That includes passwords created during registration or as the result of a password reset. If the user eventually cycles over the same password, we don't want to give away that the password has already been used.

Collapse
 
nyctonio profile image
Ritesh Kumar

Thank you very much learned something new 🥰.

Collapse
 
artis3n profile image
Ari Kalfus

Yeah!

Collapse
 
mthtanjim profile image
Tanjimul Hasan Tanjim

Can you explain about ":" which used in password:plainTextPassword

Collapse
 
nyctonio profile image
Ritesh Kumar • Edited

I am destructing req.body and storing password as plaintextpassword

image
you can understand this with this example

Collapse
 
mthtanjim profile image
Tanjimul Hasan Tanjim

Thanks you so much, I understood well.

Collapse
 
officialksolomon profile image
officialksolomon

On the area where you encrypted the password, after destructuring request body, you recreated the password.
Which is supposed to produce an error identifier already exist because const variable cannot be recreated.

Collapse
 
nyctonio profile image
Ritesh Kumar

Actually on destructuring the request body i am creating email and plaintextpassword and after that i created const password so there is no recreation 😇 thats why it didn't produced error.

Collapse
 
vyrru5 profile image
VyRru5

Why don’t use header authorization bearer in request ? Is the best practices

Collapse
 
nyctonio profile image
Ritesh Kumar • Edited

Yes we can implement it in that way too which will also make it not vulnerable to CSRF but I thought that cookie one will be more beginner friendly 😬.

Collapse
 
harshitaditya1 profile image
Harshit Aditya

Amazing Blog Ritesh.

Collapse
 
nyctonio profile image
Ritesh Kumar

Thanks harshit

Collapse
 
himanshu3651 profile image
Himanshu Singh

Quite Awesome....!!

Collapse
 
lakshay1025 profile image
Lakshay1025

Amazing pal...Great Job

Collapse
 
jayantsankhi profile image
jayantsankhi

Great work thanks for this article 👍😊

Collapse
 
harshitk816 profile image
Harshitk816

OMG! This literally made my work so easy-peasy😍. Thanks a lot for this!!! Looking forward to more posts like this💕

Collapse
 
dhruv_arora profile image
Dhruv Arora

You saved a life 🥺
I was stuck in this
Thank you brother ❤

Collapse
 
rishabh055 profile image
Rishabh Rathore

Such a Awesome Explanation Dude keep it up🔥✌🏻

Collapse
 
nyctonio profile image
Ritesh Kumar • Edited

Thanks Rishabh

Collapse
 
crackingdemon profile image
crackingdemon

No one has simple explanation than this post :) , worth spending time to read this , learned a lot :)