In this article we will learn how to implement user authentication in NodeJS using passport.js which is a authentication middleware and is extremely flexible and modular. In this tutorial we will create a simple NodeJS backend using express with just registration and login functionality to understand the use of passport.js in authentication.

In previous article we learned how to implement authentication in NodeJS using JWT, check it out here, to get in-depth understanding of authentication process in NodeJS.

Get complete source code for this implementation here.

Application Flow

  • We will first create a user registration functionality which will allow user to register using a username and password.
  • Then we will allow that user to login using the same credentials, for which we will use passport.js.
  • Then we allow the user to access certain route based on authentication status of the user using passport.js methods.
  • We will also let user logout from the application.

Setting up Express Server

Let’s first initialize a new NodeJS project so go ahead and run the below command in new folder.

npm init

This will create a package.json file in the root folder, which contains all the information about the project and its dependencies. Let’s add some npm packages that we require in our project, run the below command to add those

npm i express mongoose nodemon dotenv 

Here we have added express, a nodejs framework to help us simplify the backend code. Mongoose to help us interact with MongoDb, which we will be using to store user information. Nodemon to keep our app automatically running whenever we do any changes. Dotenv, as we will be storing all environment variables in .env file, lets just go ahead and create it.

PORT=8000
DB_URI=mongodb://localhost:27017

Here we have added the port number on which we will run our express server as well as the MongoDB URI which we will connect to store user related data.

Let’s now create a entry point to the application which will be app.js file in root folder. Code for app.js will be as follows.

// app.js - setting up express server

const express = require('express');
require('dotenv').config();
const port = process.env.PORT || "8000";

const app = express();
app.use(express.urlencoded({ extended: false }));
app.use(express.json());

//for initializing databse connection
const mongoose = require('mongoose');
const dbURI = process.env.DB_URI;
module.exports.connection = mongoose
	.connect(dbURI, {
		useNewUrlParser: true,
		useUnifiedTopology: true,
	})
	.then(() => console.log("Database Connected"))
	.catch((err) => console.log(err));

app.use('/', (req,res)=>{
    res.send('Hello from express!!')
});

app.listen(port, () => {
    console.log(`Listening to requests on http://localhost:${port}`);
  });

module.exports = app;

Here we configured a simple express server and also set a MongoDB connection using mongoose.

Let’s run the app and check if its working, use the command ‘nodemon app’ in terminal, you should see the below messages in terminal

nodemon-start

Let’s check in browser if express server is up

express-hello

As you can see, we have successfully set up NodeJS server.

User Registration in NodeJS

Let’s start adding functionality to our express application. First thing our app need is a registration feature.

For registering a user we need a user model based on schema with username and password.

// ./models/user.model.js

const mongoose = require('mongoose')

var userSchema = new mongoose.Schema({
    username: {
        type : String,
        unique : true,
        required : true
    },
    password: {
        type: String,
        required: true
    }
})

mongoose.model('User',userSchema)

Now we need our application to have access to this model, so lets import it in our app.js file. Also we will route all the requests to a separate file named routes.js in routes folder. So the code in app.js file will be as follows

// app.js - routing requests to routes.js

const express = require('express');
require('dotenv').config();
const port = process.env.PORT || "8000";
require('./models/users.model')
const usersRoute = require('./routes/routes.js');

const app = express();
app.use(express.urlencoded({ extended: false }));
app.use(express.json());

//for initializing databse connection
const mongoose = require('mongoose');
const dbURI = process.env.DB_URI;
module.exports.connection = mongoose
	.connect(dbURI, {
		useNewUrlParser: true,
		useUnifiedTopology: true,
	})
	.then(() => console.log("Database Connected"))
	.catch((err) => console.log(err));

app.use('/', usersRoute);

app.listen(port, () => {
    console.log(`Listening to requests on http://localhost:${port}`);
  });

module.exports = app;

Here we have initialised user model and also now we are routing all the requests to user route, code for which is as follows

// ./routes/routes.js

const express = require('express');
const router = express.Router();
const registerService = require('../services/register.service')

router.get('/register', (req, res) => {

    const form = '<h1>Register Page</h1><form method="post" action="/register">\
        Enter Username:<br><input type="text" name="username" required>\
        <br>Enter Password:<br><input type="password" name="password" required>\
        <br><br><input type="submit" value="Submit"></form>\
        If already registered,please <a href="/login">Login</a></p>';

    res.send(form);
    
});

router.post('/register',registerService)

module.exports = router

Here we have created a get route which contains a registration form for the user to fill and on submit we are triggering the post request for register. In post request, we have used registerService which contains registration logic, code for register.service.ts is as follows

// ./services/register.service.ts

const mongoose = require('mongoose')
const User = mongoose.model('User')
const bcrypt = require('bcrypt')

module.exports = async (req,res) => {
    try{
        const ifExists = await User.findOne({ username:req.body.username })
        if (ifExists)
            return res.send('<h1>Username already exists.</h1><p>Please <a href="/register">register</a> with another username</p>');
        const user = new User({
            username:req.body.username,
            password: bcrypt.hashSync(req.body.password,bcrypt.genSaltSync(10))
        })
        await user.save()
        return res.redirect('/login');
    }catch(err){
        console.log(err)
        return res.status(500).json('Internal Server Error')
    }
}}

Here, we are first checking if the username with which user name is trying to register already exists, if it exists we don’t register and send message back to user. If username does not exist we proceed to add user in database by using user model we created. One important thing we are doing here before saving user in database is hashing the password using bcrypt.

This completes our registration logic. Let’s move on to login.

User Login in NodeJS with Passport.js

As mentioned, we will be using passport.js for authentication, so lets just go ahead and install it first along with connect-mongo and express-session, use the below commands

npm i passport passport-local connect-mongo express-session

Here passport-local is for using local strategy which is when we want to use username, email etc for authentication. We also have other strategies like google, Facebook, twitter etc which we can use when we want to authenticate through them. For this tutorial, let’s just focus on local strategy.

Let’s initialize passport in app.js.

//app.js - initializing passport and express-session

const express = require('express');
require('dotenv').config();
const port = process.env.PORT || "8000";
require('./models/users.model')
const usersRoute = require('./routes/routes.js');

const app = express();
app.use(express.urlencoded({ extended: false }));
app.use(express.json());

//connecting to mongo databse
const mongoose = require('mongoose');

const dbURI = process.env.DB_URI;

module.exports.connection = mongoose
	.connect(dbURI, {
		useNewUrlParser: true,
		useUnifiedTopology: true,
	})
	.then(() => console.log("Database Connected"))
	.catch((err) => console.log(err));

//using connect-mongo and express session to store user sessions which is used by passport for authentication
const MongoStore = require('connect-mongo');

const session = require('express-session');
  
app.use(session({
  secret: process.env.SESSION_SECRET,
  resave: false,
  saveUninitialized: false,
  store: MongoStore.create({mongoUrl: process.env.DB_URI})
}))

//initializing passport
var passport = require('passport');

require('./config/passport-config');
app.use(passport.initialize());
app.use(passport.session());

app.use('/', usersRoute);

module.exports = app;

app.listen(port, () => {
    console.log(`Listening to requests on http://localhost:${port}`);
  });

We are first creating a user session using express session which is used by passport to check if user is authenticated. To create a session, we need a secret which we can store in our .env file. and a store, which in our case will be a collection in MongoDB. Also here we have required passport and initialized it using passport-config.js, code for which is as follows.

// ./config/passport-config.js

const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
const mongoose = require('mongoose')
const User = mongoose.model('User')
const bcrypt = require('bcrypt')

const authenticateUser = async (username, password, done) => {
    User.findOne({ username: username }).then((user) => {
        if (!user) 
            return done(null, false, { message: 'No user with that username' })
        if(bcrypt.compareSync(password,user.password))
            return done(null, user)
        else
            return done(null, false,{ message: 'wrong password' });
    }).catch((err) => {   
        done(err);
    });
}

const strategy  = new LocalStrategy(authenticateUser);

passport.use(strategy);

passport.serializeUser((user, done) => {
    done(null, user.id);
});

passport.deserializeUser((userId, done) => {
    User.findById(userId)
        .then((user) => {
            done(null, user);
        })
        .catch(err => done(err))
});

This is the file where we are handling passport authentication. Here first we have authenticateUser function which takes the username and password parameter, the fields which we have defined for login, and a done parameter, which is a method used internally by passport to return either success, error or fail options.

Inside the authenticateUser function we have our basic login logic, where we are finding the username in database, if username exists, we are checking if the password entered is correct by comparing it using bcrypt. And we are handling the negative scenarios likewise.

We then use this function to define the local strategy, which we are using in passport.use syntax.

Then we have serializeUser function which basically takes the id of the logged in user from mongo and inserts it in session and we can retrieve it from session using req.session.passport.user.

At last we have deserializeUser, which uses req.session.passport.user as id to grab the user from database and maps it to req.user.

Let’s see how to use passport.js for login. Below will be new code for routes.js

// ./routes/routes.js - adding login routes 

const express = require('express');
const router = express.Router();
const registerService = require('../services/register.service')
const passport = require('passport');

//User Registration routes
router.get('/register', (req, res) => {

    const form = '<h1>Register Page</h1><form method="post" action="/register">\
        Enter Username:<br><input type="text" name="username" required>\
        <br>Enter Password:<br><input type="password" name="password" required>\
        <br><br><input type="submit" value="Submit"></form>\
        If already registered,please <a href="/login">Login</a></p>';

    res.send(form);
    
});
router.post('/register',registerService)

//User Login routes
router.get('/login', (req, res) => {
   
    const form = '<h1>Login Page</h1><form method="POST" action="/login">\
    Enter Username:<br><input type="text" name="username" required>\
    <br>Enter Password:<br><input type="password" name="password" required>\
    <br><br><input type="submit" value="Submit"></form>';

    res.send(form);

});
router.post('/login', passport.authenticate('local', { failureRedirect: '/login-failure'}),(req,res)=>{
    res.redirect('/');
});

router.get('/login-failure', (req, res) => {
    console.log('login-failed')
    res.send(`<p>You entered the wrong password.<br>\
    <a href="/login">login</a><br>\
    <a href="/register">register</p>`);
});

module.exports = router

Here first on login get request, we providing user with a login form, which on submit calls the login post request where we are using passport.authenticate middleware which first takes the type of strategy as parameter, which in our case is ‘local’, then we give the failureRedirect path, then we can either give successRedirect or directly define the success callback as we have done.

This middleware will internally call the passport-config.js as we have initialized it in app.js.

User Authentication

Now that we have login in place. Let’s see how can we allow access to the authenticated user. We will create a default route which will only allow authenticated users. Let’s add the below code in routes.js file.

// .routes/routes.js - adding default '/' route which checks for authenticated user.

const express = require('express');
const router = express.Router();
const registerService = require('../services/register.service')
const passport = require('passport');

router.get('/', (req, res) => {
    if(req.isAuthenticated())
    {
        res.send(`<h1>Welcome ${req.user.username}, You are authenticated!!</h1>\
            <p>Click here to <a href="/logout">Logout</a></p>
        `)
        
    }
    else{
        res.send('<h1>You are not authenticated!!</h1>\
            <p>Please\
            <a href="/register">Register</a>\
            or\
            <a href="/login">Login</a></p>'
        );
    }
});


//User Registration routes
router.get('/register', (req, res) => {

    const form = '<h1>Register</h1><form method="post" action="/register">\
        Enter Username:<br><input type="text" name="username" required>\
        <br>Enter Password:<br><input type="password" name="password" required>\
        <br><br><input type="submit" value="Submit"></form>\
        If already registered,please <a href="/login">Login</a></p>';

    res.send(form);
    
});
router.post('/register',registerService)

//User Login routes
router.get('/login', (req, res) => {
   
    const form = '<h1>Login</h1><form method="POST" action="/login">\
    Enter Username:<br><input type="text" name="username" required>\
    <br>Enter Password:<br><input type="password" name="password" required>\
    <br><br><input type="submit" value="Submit"></form>';

    res.send(form);

});
router.post('/login', passport.authenticate('local', { failureRedirect: '/login-failure'}),(req,res)=>{
    res.redirect('/');
});

router.get('/login-failure', (req, res) => {
    console.log('login-failed')
    res.send(`<p>You entered the wrong password.<br>\
    <a href="/login">login</a><br>\
    <a href="/register">register</p>`);
});

router.get('/logout', (req, res, next) => {
    console.log('in')
    req.logOut((err) => {
        if (err) return next(err);        
        res.redirect('/');})
});

module.exports = router

Here we have added a default ‘/’ route which user will be routed to initially. Passport.js allows us to use req.isAuthenticated() method to check if user is authenticated using the session information we discussed above. Using that we are displaying the page accordingly.

Also we have req.logOut() method which will delete user session and the user will no longer be authenticated.

This completes our implementation of User Authentication in NodeJS using Passport.js

Testing the application

Let’s test the entire application flow in chrome. Below is the video captured for the entire process.

As you can see, we now have a working application covering registration, login and authentication using passport.js

Summary

In this article, we learned how to successfully set up a NodeJS express application and implement user registration, login and authentication in NodeJS using express, passport.js and MongoDB . We also learned how passport.js uses sessions for user authentication and how we can use methods provided by passport as middleware to check user authentication status.

Leave a Reply

Your email address will not be published. Required fields are marked *