Node.js Authentication using Passport.js

Node.js Authentication using Passport.js

Check the source code here

Authentication is an important portion for web application. From client side, like a mobile application must require a secure authentication to the server and *passport* is a prominent helper.

Here I’m developing a simple oAuth(open authentication) application and show how to secure routing.

Work-Flow

  • Two routes will be available: guest, user

  • The /guest routes can be accessed by anyone

  • The /user routes can be accessed by the authenticate users

REQUIREMENTS

  1. *Node.js(6.x)*

  2. *NPM(3.x)*

  3. *Express.js(4.x)*

APP SETUP

Dependency

Make your directory and inside the directory, create a file named package.json

mkdir myPassportAuthentication
cd myPassportAuthentication/
touch package.json

Paste the following code in your package.json file:

{
  **"name"**: **"my-passport-authentication"**,
  **"main"**: **"server.js"**,
  **"dependencies" **: {
    **"express"**: **"~4.14.0"**,
    **"mongoose"**: **"~4.6.1"**,
    **"passport"**: **"~0.3.2"**,
    **"passport-local" **: **"~1.0.0"**,
    **"bcrypt-nodejs" **: **"latest"**,
    **"morgan"**: **"~1.7.0"**,
    **"body-parser"**: **"~1.15.2"**,
    **"cookie-parser"**: **"~1.4.3"**,
    **"method-override"**: **"~2.3.6"**,
    **"express-session"**: **"~1.14.1"
  **}
}

To download the dependencies, run

npm install

So our entry point is the server.js

Server

Lets create the server.js in the same directory.

touch server.js

Now paste the following code in server.js

*// server.js

// set up ======================================================================
// get all the tools we need
***var **express  = require(**'express'**);
**var **app      = express();
**var **path     = require(**'path'**);
**var **port     = process.**env**.PORT || 8080;
**var **mongoose = require(**'mongoose'**);
**var **passport = require(**'passport'**);

**var **morgan       = require(**'morgan'**);
**var **cookieParser = require(**'cookie-parser'**);
**var **bodyParser   = require(**'body-parser'**);
**var **session      = require(**'express-session'**);

*// router files ===============================================================
***var **authRoutes   = require(**'./routes/auth'**);
**var **testRoutes   = require(**'./routes/test'**);

**var **configDB     = require(**'./config/database'**);

*// configuration ===============================================================
*mongoose.connect(configDB.**url**); *// connect to our database

*require(**'./config/passport'**)(passport); *// pass passport for configuration

// set up our express application
*app.use(morgan(**'dev'**)); *// log every request to the console
*app.use(cookieParser()); *// read cookies (needed for auth)
*app.use(bodyParser.**json**()); *// get information from html forms
*app.use(bodyParser.**urlencoded**({ **extended**: **true **}));

*// required for passport
*app.use(session({
    **secret**: **'eminem'**, *// session secret
    ***resave**: **true**,
    **saveUninitialized**: **true
**}));
app.use(passport.initialize());
app.use(passport.session()); *// persistent login sessions

// routes ======================================================================
*app.use(**'/auth'**, authRoutes);
app.use(**'/test'**, testRoutes);

*// launch ======================================================================
*app.listen(port);
**console**.log(**'The magic happens on port ' **+ port);

Routes

Create two routing files,

mkdir routes
cd routes
mkdir auth
mkdir test
cd auth
touch index.js
cd ..
cd test
touch index.js

From the both routing file

  • ./routes/auth/index.js

  • ./routes/test/index.js

Export the router module.

Paste the following code on the both file:

**var **express = require(**'express');
var **router = express.Router();

module.exports = router;

Database

Now time to configure database. Follow the following commands:

mongo
use myPassportAuth

So our database is created, now create the config file and put database info:

Go to the project root directory and

mkdir config
cd config
mkdir database
touch index.js

Inside the ./config/database/index.js paste the following code:

*// config/database.js
*module.exports = {
    **'url' **: **'mongodb://localhost/myPassportAuth'
**};

Passport

Go to the root directory and follow the command:

cd config
mkdir passport
cd passport
touch index.js

Now paste the following code to the ./config/passport/index.js

*// load all the things we need
***var **LocalStrategy    = require(**'passport-local'**).Strategy;

**var User       **= require(**'../../models/user'**);

**var ***myLocalConfig *= (passport) => {
    *// =========================================================================
    // passport session setup ==================================================
    // =========================================================================
    // required for persistent login sessions
    // passport needs ability to serialize and unserialize users out of session

    // used to serialize the user for the session
    *passport.serializeUser(**function**(user, done) {
        done(**null**, user.**id**);
    });

    *// used to deserialize the user
    *passport.deserializeUser(**function**(id, done) {
        **User**.findById(id, **function**(err, user) {
            done(err, user);
        });
    });

    *// =========================================================================
    // LOCAL LOGIN =============================================================
    // =========================================================================
    *passport.use(**'local-login'**, **new **LocalStrategy({
            *// by default, local strategy uses username and password, we will override with email
            ***usernameField **: **'email'**,
            **passwordField **: **'password'**,
            **passReqToCallback **: **true ***// allows us to pass in the req from our route (lets us check if a user is logged in or not)
        *},
        **function**(req, email, password, done) {
            **if **(email)
                email = email.toLowerCase(); *// Use lower-case e-mails to avoid case-sensitive e-mail matching

            // asynchronous
            *process.nextTick(**function**() {
                **User**.findOne({ **'local.email' **:  email }, **function**(err, user) {
                    *// if there are any errors, return the error
                    ***if **(err)
                        **return **done(err);

                    *// if no user is found, return the message
                    ***if **(!user)
                        **return **done(**null**, **false**);

                    **if **(!user.validPassword(password))
                        **return **done(**null**, **false**);

                    *// all is well, return user
                    ***else
                        return **done(**null**, user);
                });
            });

        }));

    *// =========================================================================
    // LOCAL SIGNUP ============================================================
    // =========================================================================
    *passport.use(**'local-signup'**, **new **LocalStrategy({
            *// by default, local strategy uses username and password, we will override with email
            ***usernameField **: **'email'**,
            **passwordField **: **'password'**,
            **passReqToCallback **: **true ***// allows us to pass in the req from our route (lets us check if a user is logged in or not)
        *},
        **function**(req, email, password, done) {
            **if **(email)
                email = email.toLowerCase(); *// Use lower-case e-mails to avoid case-sensitive e-mail matching

            // asynchronous
            *process.nextTick(**function**() {
                *// if the user is not already logged in:
                ***if **(!req.**user**) {
                    **User**.findOne({ **'local.email' **:  email }, **function**(err, user) {
                        *// if there are any errors, return the error
                        ***if **(err)
                            **return **done(err);

                        *// check to see if theres already a user with that email
                        ***if **(user) {
                            **return **done(**null**, **false**);
                        } **else **{

                            *// create the user
                            ***var **newUser            = **new User**();

                            newUser.local.**email    **= email;
                            newUser.local.**password **= newUser.generateHash(password);

                            newUser.save(**function**(err) {
                                **if **(err)
                                    **return **done(err);

                                **return **done(**null**, newUser);
                            });
                        }

                    });
                    *// if the user is logged in but has no local account...
                *} **else if **( !req.**user**.**local**.**email **) {
                    *// ...presumably they're trying to connect a local account
                    // BUT let's check if the email used to connect a local account is being used by another user
                    ***User**.findOne({ **'local.email' **:  email }, **function**(err, user) {
                        **if **(err)
                            **return **done(err);

                        **if **(user) {
                            **return **done(**null**, **false**);
                            *// Using 'loginMessage instead of signupMessage because it's used by /connect/local'
                        *} **else **{
                            **var **user = req.**user**;
                            user.**local**.**email **= email;
                            user.**local**.**password **= user.generateHash(password);
                            user.save(**function **(err) {
                                **if **(err)
                                    **return **done(err);

                                **return **done(**null**,user);
                            });
                        }
                    });
                } **else **{
                    *// user is logged in and already has a local account. Ignore signup. (You should log out before trying to create a new account, user!)
                    ***return **done(**null**, req.**user**);
                }

            });

        }));
};

module.exports = *myLocalConfig*;

Model

Again go to the root directory and follow the commands

mkdir models
cd models
mkdir user
cd user
touch index.js

Paste the following code and make the user model:

*// load the things we need
***var **mongoose = require(**'mongoose'**);
**var **bcrypt   = require(**'bcrypt-nodejs'**);

*// define the schema for our Photographer model
***var **userSchema = mongoose.Schema({

    **local            **: {
        **email        **: String,
        **password     **: String
    }
});

*// generating a hash
*userSchema.**methods**.generateHash = **function**(password) {
    **return **bcrypt.*hashSync*(password, bcrypt.*genSaltSync*(8), **null**);
};

*// checking if password is valid
*userSchema.**methods**.validPassword = **function**(password) {
    **return **bcrypt.*compareSync*(password, **this**.**local**.**password**);
};

*// create the model for photographers and expose it to our app
*module.exports = mongoose.model(**'User'**, userSchema);

Boilerplate Completion

Here’s our boilerplate code is ready. Go to the root directory and run

node server.js

You must see: “The magic happens on port 8080”

Let’s complete the auth route. Paste the code in ./routes/auth/index.js

**var **express = require(**'express'**);
**var **router = express.Router();
**var **passport = require(**'passport'**);

router.**post**(**'/signup'**, passport.authenticate(**'local-signup'**, {
    **successRedirect **: **'/auth/profile'**,
    **failureRedirect **: **'auth/signup'
**}));

router.**post**(**'/login'**, passport.authenticate(**'local-login'**, {
    **successRedirect **: **'/auth/profile'**,
    **failureRedirect **: **'auth/login'
**}));
router.get(**'/profile'**, *isLoggedIn*, (req, res) => {
    res.**status**(200).**json**(req.**user**);
});
router.get(**'/logout'**, *isLoggedIn*, (req, res) => {
    req.logout();
    res.**status**(200).**json**({
        **'message'**: **'successfully logout'
    **});
});

module.exports = router;

*//route middleware to ensure user is logged in
***function ***isLoggedIn*(req, res, next) {
    **if **(req.isAuthenticated())
        **return **next();
    res.**status**(400).**json**({
        **'message'**: **'access denied'
    **});
}

So we are done for signIn, signOut, SignUp and viewProfile Section.

Secure Routing

Here we create two routing, one for the guest and another for the authenticated users.

Go to the ./routes/test/index.js and it would be like this:

**var **express = require(**'express'**);
**var **router = express.Router();

router.get(**'/guest'**, (req, res) => {
    res.**status**(200).**json**({
        **'message'**: **"This is not secured guest routing"
    **});
});

router.get(**'/user'**, *isLoggedIn*, (req, res) => {
    res.**status**(200).**json**({
        **'message'**: **"This is secured user routing"
    **});
});

module.exports = router;

*//route middleware to ensure user is logged in
***function ***isLoggedIn*(req, res, next) {
    **if **(req.isAuthenticated())
        **return **next();
    res.**status**(400).**json**({
        **'message'**: **'access denied'
    **});
}

Congratulation

If you have come this far, then congratulation, you implements the basic open authorization using passport js and secure specific routing.

API Test

We will use postman to check the API.

Create an user:

Request Type: POST Headers: { ‘content-type’: ‘application/json’ } Address: localhost:8080/auth/signup Body: raw


{
     “email”: “test[@mail.com](mailto:mail2@mail.com)”,
     “password”: “1234567890”
}

In response we will get user id and hashed password:

{
  “_id”: “59c7d5f7294b322b0321ad6a”,
  “__v”: 0,
  “local”: {
     “password”:  “$2a$08$varXSszNbVsFH85B3Ewk4.Q4qhXRJBeyDrZNLu7vrlJhoBefpc50S”,
     “email”: “[test@mail.com](mailto:test@mail.com)”
 }
}

When we create an user we will be automatically logged in.

Now Let say, A user already exist and wants to logged in,

Log in an User:

Request Type: POST Headers: { ‘content-type’: ‘application/json’ } Address: localhost:8080/auth/login Body: raw

{
     “email”: “test[@mail.com](mailto:mail2@mail.com)”,
     “password”: “1234567890”
}

In response we will get Logged in user information:

{
  “_id”: “59c7d5f7294b322b0321ad6a”,
  “__v”: 0,
  “local”: {
   “password”: “$2a$08$varXSszNbVsFH85B3Ewk4.Q4qhXRJBeyDrZNLu7vrlJhoBefpc50S”,
   “email”: “[test@mail.com](mailto:test@mail.com)”
 }
}

Profile of a logged in user:

To get the information of a logged in user,

Request Type: GET Address: localhost:8080/auth/profile

In response we will get the users profile:

{
  “_id”: “59c7d5f7294b322b0321ad6a”,
  “__v”: 0,
  “local”: {
   “password”: “$2a$08$varXSszNbVsFH85B3Ewk4.Q4qhXRJBeyDrZNLu7vrlJhoBefpc50S”,
   “email”: “[test@mail.com](mailto:test@mail.com)”
 }
}

Log out of an user:

To log out from the app,

Request Type: GET Address: localhost:8080/auth/logout

In response we will get the message:

{
    "message": "successfully logout"
}

Open authorization is completed here.

Securing Routing

When user is not logged in,

Request Type: GET Address: localhost:8080/test/user

{
     “message”: “access denied”
}

Request Type: GET Address: localhost:8080/test/guest

{
     “message”: “This is not secured guest routing”
}

When user is logged in,

Request Type: GET Address: localhost:8080/test/user

{
     “message”: “access denied”
}

Request Type: GET Address: localhost:8080/test/guest

{
     “message”: “This is not secured guest routing”
}

Conclusion

So Here we are, completed the basic open authentication using passport.js in node.js and express.js server. In future I will go through authentication using facebook, google, twitter and like other stuff. Stay tuned and if there is a confusing term or something, response below or create an issue in *github*.