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

Discussion (26)

Collapse
charlyjazz profile image
Carlos Azuaje

JWT For session is a bad design.

Collapse
nyctonio profile image
Ritesh Kumar Author • 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
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 Author

๐Ÿฅบ thank you

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 Author

Yes you are right from now i will also use the same thanks for pointing it out ๐Ÿค

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 Author

Thank you very much learned something new ๐Ÿฅฐ.

Collapse
artis3n profile image
Ari Kalfus

Yeah!

Collapse
harshitaditya1 profile image
Harshit Aditya

Amazing Blog Ritesh.

Collapse
nyctonio profile image
Ritesh Kumar Author

Thanks harshit

Collapse
vyrru5 profile image
VyRru5

Why donโ€™t use header authorization bearer in request ? Is the best practices

Collapse
nyctonio profile image
Ritesh Kumar Author • 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
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 Author

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
rishabh055 profile image
Rishabh Rathore

Such a Awesome Explanation Dude keep it up๐Ÿ”ฅโœŒ๐Ÿป

Collapse
nyctonio profile image
Ritesh Kumar Author • Edited

Thanks Rishabh

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
himanshu3651 profile image
Himanshu Singh

Quite Awesome....!!

Collapse
dhruv_arora profile image
Dhruv Arora

You saved a life ๐Ÿฅบ
I was stuck in this
Thank you brother โค

Collapse
lakshay1025 profile image
Lakshay1025

Amazing pal...Great Job

Collapse
mmeurer00 profile image
Maxine Meurer

@icecoffee is this the answer ?!

Collapse
icecoffee profile image
atulit023 • Edited

Here the author is talking about server side authentication.

Collapse
crackingdemon profile image
crackingdemon

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