Tutorial

Easy Node Authentication: Setup and Local

Draft updated on Invalid Date
Default avatar

By Chris on Code

Easy Node Authentication: Setup and Local

This tutorial is out of date and no longer maintained.

Note: This article is part of our Easy Node Authentication series.

Introduction

Authentication and logins in Node can be a complicated thing. Actually logging in for any application can be a pain. This article series will deal with authenticating in your Node application using the package Passport.

Note: Updates

Edit 11/18/2017: Updated to reflect Facebook API changes. Updating dependencies in package.json. Edit #1: Changed password hashing to be handled inside user model and asynchronously. Edit #2: Changed password hashing to be explicitly called. Helps with future tutorials.

What we’ll be building:

We will build an application that will have:

  • Local account logins and signups (using passport-local)
  • Facebook logins and registration (using passport-facebook)
  • Twitter logins and registration (using passport-twitter)
  • Google+ logins and registration (using oauth with passport-google-oauth)
  • Require login for certain routes/sections of your application
  • Creating a password hash for local accounts (using bcrypt-nodejs)
  • Displaying error messages (using flash with connect-flash. required since express 3.x)
  • Linking all social accounts under one user account
  • Allowing a user to unlink a specific social account

Enough chit-chat. Let’s dive right into a completely blank Node application and build our entire application from scratch.

Here’s what we’ll be building:

And after a user has logged in with all their credentials:

For this article, we’ll be focusing on setup and only local logins and registrations/signups. Since this is the first article and also deals with setting up our application, it will probably be one of the longer ones if not the longest. Sit tight for the duration of your flight.

Setting Up Our Application

To set up our base Node application, we’ll need a few things. We’ll set up our npm packages, node application, configuration files, models, and routes.

Application Structure

        - app
        ------ models
        ---------- user.js  <!-- our user model -->
        ------ routes.js    <!-- all the routes for our application -->
        - config
        ------ auth.js      <!-- will hold all our client secret keys (facebook, twitter, google) -->
        ------ database.js  <!-- will hold our database connection settings -->
        ------ passport.js  <!-- configuring the strategies for passport -->
        - views
        ------ index.ejs    <!-- show our home page with login links -->
        ------ login.ejs    <!-- show our login form -->
        ------ signup.ejs   <!-- show our signup form -->
        ------ profile.ejs  <!-- after a user logs in, they will see their profile -->
        - package.json      <!-- handle our npm packages -->
        - server.js         <!-- setup our application -->

Go ahead and create all those files and folders and we’ll fill them in as we go along.

Packages package.json

We are going to install all the packages needed for the entire tutorial series. This means we’ll install all the packages needed for passport local, facebook, twitter, google, and the other things we need. It’s all commented out so you know what each does.

package.json
    {
      "name": "node-authentication",
      "main": "server.js",
      "dependencies" : {
        "express" : "~4.14.0",
        "ejs" : "~2.5.2",
        "mongoose" : "~4.13.1",
        "passport" : "~0.3.2",
        "passport-local" : "~1.0.0",
        "passport-facebook" : "~2.1.1",
        "passport-twitter" : "~1.0.4",
        "passport-google-oauth" : "~1.0.0",
        "connect-flash" : "~0.1.1",
        "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"
      }
    }

Most of these are pretty self-explanatory.

  • Express is the framework.
  • Ejs is the templating engine.
  • Mongoose is object modeling for our MongoDB database.
  • Passport stuff will help us authenticating with different methods.
  • Connect-flash allows for passing session flashdata messages.
  • Bcrypt-nodejs gives us the ability to hash the password.

I use bcrypt-nodejs instead of bcrypt since it is easier to set up in Windows.

Now that we have all of our dependencies ready to go, let’s go ahead and install them:

npm install With all of our packages ready to go, let’s set up our application in server.js.

Application Setup server.js

Let’s make all our packages work together nicely. Our goal is to set up this file and try to have it bootstrap our entire application. We’d like to not go back into this file if it can be helped. This file will be the glue for our entire application.

server.js
    // set up ======================================================================
    // get all the tools we need
    var express  = require('express');
    var app      = express();
    var port     = process.env.PORT || 8080;
    var mongoose = require('mongoose');
    var passport = require('passport');
    var flash    = require('connect-flash');

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

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

    // 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()); // get information from html forms

    app.set('view engine', 'ejs'); // set up ejs for templating

    // required for passport
    app.use(session({ secret: 'ilovescotchscotchyscotchscotch' })); // session secret
    app.use(passport.initialize());
    app.use(passport.session()); // persistent login sessions
    app.use(flash()); // use connect-flash for flash messages stored in session

    // routes ======================================================================
    require('./app/routes.js')(app, passport); // load our routes and pass in our app and fully configured passport

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

We are going to comment out our passport configuration for now. We’ll uncomment it after we create that config/passport.js file.

The path of our passport object is important to note here. We will create it at the very beginning of the file with var passport = require('passport');. Then we pass it into our config/passport.js file for it to be configured. Then we pass it to the app/routes.js file for it to be used in our routes.

Now with this file, we have our application listening on port 8080. All we have to do to start up our server is:

node server.js Then when we visit http://localhost:8080 we will see our application. (Not really right this moment since we have some more set up to do)

Auto Refreshing: By default, Node.js doesn’t automatically refresh our server every time we change files. To do that we’ll use nodemon. Just install with: npm install -g nodemon and use with: nodemon server.js.

Now, this won’t do much for our application since we don’t have our database configuration, routes, user model, or passport configuration set up. Let’s do the database and routes now.

Database Config config/database.js

We already are calling this file in server.js. Now we just have to set it up.

config/database.js
    module.exports = {

        'url' : 'your-settings-here' // looks like mongodb://<user>:<pass>@mongo.onmodulus.net:<port>

    };

Fill this in with your own database. If you don’t have a MongoDB database lying around, I would suggest going to Modulus.io and grabbing one. Once you sign up (and you get a $15 credit for signing up), you can create your database, grab its connection URL, and place it in this file.

You can also install MongoDB locally and use a local database. You can find instructions here: An Introduction to MongoDB.

Routes app/routes.js

We will keep our routes simple for now. We will have the following routes:

  • Home Page (/)
  • Login Page (/login)
  • Signup Page (/signup)
  • Handle the POST for both login
  • Handle the POST for both signup
  • Profile Page (after logged in)
app/routes.js
    module.exports = function(app, passport) {

        // =====================================
        // HOME PAGE (with login links) ========
        // =====================================
        app.get('/', function(req, res) {
            res.render('index.ejs'); // load the index.ejs file
        });

        // =====================================
        // LOGIN ===============================
        // =====================================
        // show the login form
        app.get('/login', function(req, res) {

            // render the page and pass in any flash data if it exists
            res.render('login.ejs', { message: req.flash('loginMessage') });
        });

        // process the login form
        // app.post('/login', do all our passport stuff here);

        // =====================================
        // SIGNUP ==============================
        // =====================================
        // show the signup form
        app.get('/signup', function(req, res) {

            // render the page and pass in any flash data if it exists
            res.render('signup.ejs', { message: req.flash('signupMessage') });
        });

        // process the signup form
        // app.post('/signup', do all our passport stuff here);

        // =====================================
        // PROFILE SECTION =====================
        // =====================================
        // we will want this protected so you have to be logged in to visit
        // we will use route middleware to verify this (the isLoggedIn function)
        app.get('/profile', isLoggedIn, function(req, res) {
            res.render('profile.ejs', {
                user : req.user // get the user out of session and pass to template
            });
        });

        // =====================================
        // LOGOUT ==============================
        // =====================================
        app.get('/logout', function(req, res) {
            req.logout();
            res.redirect('/');
        });
    };

    // route middleware to make sure a user is logged in
    function isLoggedIn(req, res, next) {

        // if user is authenticated in the session, carry on
        if (req.isAuthenticated())
            return next();

        // if they aren't redirect them to the home page
        res.redirect('/');
    }

app.post: For now, we will comment out the routes for handling the form POST. We do this since passport isn’t set up yet.

req.flash: This is the connect-flash way of getting flashdata in the session. We will create the loginMessage inside our passport configuration.

isLoggedIn: Using route middleware, we can protect the profile section route. A user has to be logged in to access that route. Using the isLoggedIn function, we will kick a user back to the home page if they try to access http://localhost:8080/profile and they are not logged in.

Logout: We will handle log out by using req.logout() provided by passport. After logging out, redirect the user to the home page.

With our server running, we can visit our application in our browser athttp://localhost:8080. Once again, we won’t see much since we haven’t made our views. Let’s go do that now. (We’re almost to the authentication stuff, I promise).

Views views/index.ejs, views/login.ejs, views/signup.ejs

Here we’ll define our views for our home page, login page, and signup/registration page.

Home Page views/index.ejs

Our home page will just show links to all our forms of authentication.

views/index.ejs
    <!doctype html>
    <html>
    <head>
        <title>Node Authentication</title>
        <link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.2/css/bootstrap.min.css"> <!-- load bootstrap css -->
        <link rel="stylesheet" href="//netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.min.css"> <!-- load fontawesome -->
        <style>
            body        { padding-top:80px; }
        </style>
    </head>
    <body>
    <div class="container">

        <div class="jumbotron text-center">
            <h1><span class="fa fa-lock"></span> Node Authentication</h1>

            <p>Login or Register with:</p>

            <a href="/login" class="btn btn-default"><span class="fa fa-user"></span> Local Login</a>
            <a href="/signup" class="btn btn-default"><span class="fa fa-user"></span> Local Signup</a>
        </div>

    </div>
    </body>
    </html>

Now if we visit our app in our browser, we’ll have a site that looks like this:

Here are the views for our login and signup pages also.

Login Form views/login.ejs

views/login.ejs
    <!doctype html>
    <html>
    <head>
        <title>Node Authentication</title>
        <link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.2/css/bootstrap.min.css"> <!-- load bootstrap css -->
        <link rel="stylesheet" href="//netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.min.css"> <!-- load fontawesome -->
        <style>
            body        { padding-top:80px; }
        </style>
    </head>
    <body>
    <div class="container">

    <div class="col-sm-6 col-sm-offset-3">

        <h1><span class="fa fa-sign-in"></span> Login</h1>

        <!-- show any messages that come back with authentication -->
        <% if (message.length > 0) { %>
            <div class="alert alert-danger"><%= message %></div>
        <% } %>

        <!-- LOGIN FORM -->
        <form action="/login" method="post">
            <div class="form-group">
                <label>Email</label>
                <input type="text" class="form-control" name="email">
            </div>
            <div class="form-group">
                <label>Password</label>
                <input type="password" class="form-control" name="password">
            </div>

            <button type="submit" class="btn btn-warning btn-lg">Login</button>
        </form>

        <hr>

        <p>Need an account? <a href="/signup">Signup</a></p>
        <p>Or go <a href="/">home</a>.</p>

    </div>

    </div>
    </body>
    </html>

Signup Form views/signup.ejs

views/signup.ejs
    <!doctype html>
    <html>
    <head>
        <title>Node Authentication</title>
        <link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.2/css/bootstrap.min.css"> <!-- load bootstrap css -->
        <link rel="stylesheet" href="//netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.min.css"> <!-- load fontawesome -->
        <style>
            body        { padding-top:80px; }
        </style>
    </head>
    <body>
    <div class="container">

    <div class="col-sm-6 col-sm-offset-3">

        <h1><span class="fa fa-sign-in"></span> Signup</h1>

        <!-- show any messages that come back with authentication -->
        <% if (message.length > 0) { %>
            <div class="alert alert-danger"><%= message %></div>
        <% } %>

        <!-- LOGIN FORM -->
        <form action="/signup" method="post">
            <div class="form-group">
                <label>Email</label>
                <input type="text" class="form-control" name="email">
            </div>
            <div class="form-group">
                <label>Password</label>
                <input type="password" class="form-control" name="password">
            </div>

            <button type="submit" class="btn btn-warning btn-lg">Signup</button>
        </form>

        <hr>

        <p>Already have an account? <a href="/login">Login</a></p>
        <p>Or go <a href="/">home</a>.</p>

    </div>

    </div>
    </body>
    </html>

Authenticating With Passport Locally

Finally! We have finally set up our application and have gotten to the authentication part. Don’t worry. The rest of the authentication articles in this tutorial series will use the same base so we won’t have to set up our application again.

So far we have installed our packages, set up our application, connected to our database, created our routes, and created our views.

Now we will create our user model, configure passport for local authentication, and use our configured passport to process our login/signup forms.

User Model

We will create our user model for the entire tutorial series. Our user will have the ability to be linked to multiple social accounts and to a local account. For local accounts, we will be keeping email and password. For the social accounts, we will be keeping their id, token, and some user information.

You can change these fields out to be whatever you want. You can authenticate locally using username and password (passport-local actually uses username by default but we’ll change that to email).

app/models/user.js
    // load the things we need
    var mongoose = require('mongoose');
    var bcrypt   = require('bcrypt-nodejs');

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

        local            : {
            email        : String,
            password     : String,
        },
        facebook         : {
            id           : String,
            token        : String,
            name         : String,
            email        : String
        },
        twitter          : {
            id           : String,
            token        : String,
            displayName  : String,
            username     : String
        },
        google           : {
            id           : String,
            token        : String,
            email        : String,
            name         : String
        }

    });

    // methods ======================
    // 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 users and expose it to our app
    module.exports = mongoose.model('User', userSchema);

Our model is done. We will be hashing our password within our user model before it saves to the database. This means we don’t have to deal with generating the hash ourselves. It is all handled nicely and neatly inside our user model.

Let’s move on to the important stuff of this article: authenticating locally!

Configuring Passport for Local Accounts

All the configuration for passport will be handled in config/passport.js. We want to keep this code in its own file away from our other main files like routes or the server file. I have seen some implementations where passport will be configured in random places. I believe having it in this config file will keep your overall application clean and concise.

So far, we have created our passport object in server.js, and then we pass it to our config/passport.js file. This is where we configure our Strategy for local, facebook, twitter, and google. This is also the file where we will create the serializeUser and deserializeUser functions to store our user in session.

I would highly recommend going to read the passport docs to understand more about how the package works.

Handling Signup/Registration

We will be handling login and signup in config/passport.js. Let’s look at signup first.

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

    // load up the user model
    var User            = require('../app/models/user');

    // expose this function to our app using module.exports
    module.exports = function(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 SIGNUP ============================================================
        // =========================================================================
        // we are using named strategies since we have one for login and one for signup
        // by default, if there was no name, it would just be called 'local'

        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 back the entire request to the callback
        },
        function(req, email, password, done) {

            // asynchronous
            // User.findOne wont fire unless data is sent back
            process.nextTick(function() {

            // find a user whose email is the same as the forms email
            // we are checking to see if the user trying to login already exists
            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, req.flash('signupMessage', 'That email is already taken.'));
                } else {

                    // if there is no user with that email
                    // create the user
                    var newUser            = new User();

                    // set the user's local credentials
                    newUser.local.email    = email;
                    newUser.local.password = newUser.generateHash(password);

                    // save the user
                    newUser.save(function(err) {
                        if (err)
                            throw err;
                        return done(null, newUser);
                    });
                }

            });

            });

        }));

    };

We have now provided a strategy to passport called local-signup. We will use this strategy to process our signup form. Let’s open up our app/routes.js and handle the POST for our signup form.

app/routes.js
    ...

        // process the signup form
        app.post('/signup', passport.authenticate('local-signup', {
            successRedirect : '/profile', // redirect to the secure profile section
            failureRedirect : '/signup', // redirect back to the signup page if there is an error
            failureFlash : true // allow flash messages
        }));

    ...

That’s all the code we need for the route. All of the heavy-duty stuff lives inside of config/passport.js. All we have to set here is where our failures and successes get redirected. Super clean.

There is also much more you can do with this. Instead of specifying a successRedirect, you could use a callback and take more control over how your application works. Here is a great stackoverflow answer on error handling. It explains how to use done() and how to be more specific with your handling of a route.

Testing Signup

With our passport config finally laid out, we can uncomment that line in our server.js. This will load our config and then we can use our signup form.

server.js
    ...

        // uncomment this line
        require('./config/passport')(passport); // pass passport for configuration

    ...

Now that we have passport, our routes, and our redirects in place, let’s go ahead and test our signup form. In your browser, go to http://localhost:8080/signup and fill out your form.

If all goes according to plan, you should be logged in, your user saved in the session, and you are redirected to the /profile page (the profile page will show nothing right now since we haven’t defined that view).

If we look in our database, we’ll also see our user sitting there cozily with all the credentials we created for him.

Exploring Your Database: I use Robomongo to see what’s in my database. Just download it and connect to your database to see your new users after they signup!

With users able to sign up, let’s give them a way to log in.

Login

This will be very similar to the signup strategy. We’ll add the strategy to our config/passport.js and the route in app/routes.js.

config/passport.js
    ...

        // =========================================================================
        // LOCAL LOGIN =============================================================
        // =========================================================================
        // we are using named strategies since we have one for login and one for signup
        // by default, if there was no name, it would just be called 'local'

        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 back the entire request to the callback
        },
        function(req, email, password, done) { // callback with email and password from our form

            // find a user whose email is the same as the forms email
            // we are checking to see if the user trying to login already exists
            User.findOne({ 'local.email' :  email }, function(err, user) {
                // if there are any errors, return the error before anything else
                if (err)
                    return done(err);

                // if no user is found, return the message
                if (!user)
                    return done(null, false, req.flash('loginMessage', 'No user found.')); // req.flash is the way to set flashdata using connect-flash

                // if the user is found but the password is wrong
                if (!user.validPassword(password))
                    return done(null, false, req.flash('loginMessage', 'Oops! Wrong password.')); // create the loginMessage and save it to session as flashdata

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

        }));

    };

We have now provided a strategy to passport called local-login. We will use this strategy to process our login form. We can check if a user exists, if the password is wrong, and set flash data to show error messages. Let’s open up our app/routes.js and handle the POST for our login form.

app/routes.js
    ...

        // process the login form
        app.post('/login', passport.authenticate('local-login', {
            successRedirect : '/profile', // redirect to the secure profile section
            failureRedirect : '/login', // redirect back to the signup page if there is an error
            failureFlash : true // allow flash messages
        }));

    ...

If you try to log in with a user email that doesn’t exist in our database, you will see the error. The same goes for if your password is wrong.

Displaying User and Secure Profile Page views/profile.ejs

Now we have functional signup and login forms. If a user is successful in authenticating they will be redirected to the profile page. If they are not successful, they will go home. The last thing we need to do is make our profile page so that those that are lucky enough to signup (all of us?) will have an exclusive place of our site all to themselves.

views/profile.ejs
    <!doctype html>
    <html>
    <head>
        <title>Node Authentication</title>
        <link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.2/css/bootstrap.min.css">
        <link rel="stylesheet" href="//netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.min.css">
        <style>
            body        { padding-top:80px; word-wrap:break-word; }
        </style>
    </head>
    <body>
    <div class="container">

        <div class="page-header text-center">
            <h1><span class="fa fa-anchor"></span> Profile Page</h1>
            <a href="/logout" class="btn btn-default btn-sm">Logout</a>
        </div>

        <div class="row">

            <!-- LOCAL INFORMATION -->
            <div class="col-sm-6">
                <div class="well">
                    <h3><span class="fa fa-user"></span> Local</h3>

                        <p>
                            <strong>id</strong>: <%= user._id %><br>
                            <strong>email</strong>: <%= user.local.email %><br>
                            <strong>password</strong>: <%= user.local.password %>
                        </p>

                </div>
            </div>

        </div>

    </div>
    </body>
    </html>

After a user logs in, they can see all their information. It is grabbed from the session and passed to our view in the app/routes.js file. We will also provide a link to log out.

Conclusion

There you have it! We’ve built a brand new application from scratch and have the ability to let users signup/register and log in. We even have support for flash messages, hashing passwords, and requiring a login for some sections of our site using route middleware.

Coming up next we’ll be looking at how to take this same structure, and use passport to authenticate with Facebook, Twitter, and Google. After that, we’ll look at how we can get all these things working together in the same application. Users will be able to log in with one type of account, and then link their other accounts.

As always, if you see any ways to improve this or need any clarification, sound off in the comments and we’ll respond pretty close to immediately… pretty close.

Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.

Learn more about us


About the authors
Default avatar
Chris on Code

author

Still looking for an answer?

Ask a questionSearch for more help

Was this helpful?
 
Leave a comment


This textbox defaults to using Markdown to format your answer.

You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!

Try DigitalOcean for free

Click below to sign up and get $200 of credit to try our products over 60 days!

Sign up

Join the Tech Talk
Success! Thank you! Please check your email for further details.

Please complete your information!

Get our biweekly newsletter

Sign up for Infrastructure as a Newsletter.

Hollie's Hub for Good

Working on improving health and education, reducing inequality, and spurring economic growth? We'd like to help.

Become a contributor

Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.

Welcome to the developer cloud

DigitalOcean makes it simple to launch in the cloud and scale up as you grow — whether you're running one virtual machine or ten thousand.

Learn more
DigitalOcean Cloud Control Panel