Skip to content

User Authentication in Node.js (Part 2 of 2)

Part 1: https://jayvolr.me/2017/08/23/user-authentication-in-node-js-part-1-of-2/

The first part of this two part series walked you through setting up the login functionality of an authentication strategy using Express, passport.js and MongoDB. In this second part, you’ll learn how to allow new users to create an account, how to securely store passwords, and how to use Redis to store sessions. I’ll also create a small notes application at the end to show off our authentication system in action.

Registration

We’ll be picking back up from the 4-Authentication branch of the GitHub repo found here: https://github.com/jayvolr/nodejs-auth

The first thing we should do in order to allow users to create an account on our application is to create a signup form. For simplicity sake, I’ll add the following form inside of the index.hjs file just below the login form:

    <h1>Create Account</h1>
    <form id="registerForm" action="/register" method="post">
      <input type="text" name="username" placeholder="Email">
      <br><br>
      <input type="password" name="password" placeholder="Password">
      <br><br>
      <input type="password" name="password2" placeholder="Password (repeat)">
      <br><br>
      <input type="submit" value="Register">
    </form>

This form is very similar to the login form, but we’re asking them to supply their chosen password twice. You might already know that the reason most applications or websites do this is to ensure that the user hasn’t mistyped their password and won’t need to request a password reset right away.

When this form is submitted, it’s going to send a POST request to the /register route. This can be seen by looking at the method and action attributes of the form.

To intercept this request, we need to listen for it the same way we did with the login form. Inside of auth.js, add the following /register route:

  .post('/register', (req, res) => {
    res.send(req.body);
  });

(make sure your semicolon is at the end of the router cascade) 

Before we go further, we can test that what we’ve added so far is working. Start up your server and submit some data with the register form. After submitting you should see the data displayed in JSON.

Now that we know the form is working and express is receiving the data, hook it up to passport like so:

 .post('/register', passport.authenticate('local-register', {
    successRedirect: '/session',
    failureRedirect: 'back'
  }));

Alright, now we can open up passport.js and define the local-register strategy we just pointed the register form to:

passport.use("local-register", new LocalStrategy({
  passReqToCallback: true
}, register))

This is very similar to what we did for the authenticate strategy, but we’re specifying the name of the strategy here in accordance with what we put inside auth.js. We didn’t need to do this before because we just used the default “local” name.

We also passed in an options object as the first argument in the LocalStrategy constructor and set passReqToCallback to true. This passes in the req parameter from express as the first argument of our callback function. We’re doing this so that we can access the password2 field, which isn’t included by default, and check that it matches the first password supplied.

Password Security

Before defining the register function we supplied for the local-register strategy, we need to make sure we have the tools to securely store the user’s password. To secure passwords, we’ll be using a bcrypt implementation in JavaScript called bcryptjs.

Install bcryptjs:

npm i -S bcryptjs

Require bcryptjs inside passport.js:

const bcrypt = require('bcryptjs');

Now we can move on to defining the register function:

function register(req, email, password, done) {
  if (req.body.password2 !== password) {
    return done(null, false, { message: "Passwords do not match." });
  }

  mongo.db.collection('users')
  .findOne({ email: email }, (err, user) => {
    if (err) return done(err);
    if (user) {
      return done(null, false, { message: "User with that email already exists." });
    }

    bcrypt.hash(password, 8, (err, hashedPassword) => {
      if (err) return done(err);
      const newUser = {
        email: email,
        password: hashedPassword,
        joinDate: new Date()
      }

      mongo.db.collection('users')
      .insert(newUser, (err, result) => {
        if (err) return done(err);
        return done(null, newUser);
      });        
    });  
  });
}

(To actually display the message being passed to the done function, look into using the connect-flash package alongside passport.)

The register function gets passed all the same arguments as the authenticate function, with the addition of the req parameter at the front if passReqToCallback has been set to true (it’s also possible to provide the authenticate function with the req parameter).

The first thing we do inside the register function is check if the two passwords supplied match. If they don’t, we return done(null, false, { … }). Second, we search the database to see if a user with the supplied email already exists. If one does, we again return done(null, false, { … }). If the function gets past the previous two conditions we’re able to assume the email provided is unused (though we haven’t validated it as a proper email, you can do so if you’d like) and the two passwords provided match.

Next we use the bcrypt.hash() method to asynchronously hash the user’s password; passing in the password, the salt or length of salt to use (8 is fine), and a callback with the parameters err and hashedPassword. Inside the callback, if there is an error we should return done(err). Otherwise, we can create the newUser object and insert it into the database. Finish by returning done(null, newUser) and the user will then be registered and logged in to their new account!

These new users won’t be able to login with the login form currently though, because our authenticate function still expects passwords to be in plain text. We can correct this by refactoring the authenticate function to use the asynchronous bcrypt.compare() method to compare the supplied password with the hash stored in the database. This is what that should look like:

function authenticate(email, password, done) {
  mongo.db.collection('users')
  .findOne({ email: email }, (err, user) => {
    if (err) return done(err);
    if (!user) return done(null, false, { message: "Incorrect email or password." });

    bcrypt.compare(password, user.password, (err, passwordMatches) => {
      if (passwordMatches === false) {
        return done(null, false, { message: "Incorrect email or password." });
      }else {
        return done(null, user);
      }
    });
  });
}

New users will now be able to login as expected.

Code for this section: https://github.com/jayvolr/nodejs-auth/tree/5-Registration

Redis Sessions

Now let’s deal with the problem where if your server goes down—say you’re pushing an update—all session data is lost, effectively logging out every user. Luckily, this is a pretty easy problem to fix.

We’ll be using Redis to persist session data. Redis is a “data structure store” that is able to persist data. To read more about Redis click here. First things first, you need to have Redis installed and running on your system (or use a cloud provider, but we won’t go that far in this article). Head over to https://redis.io/download and download Redis.

Note: Unfortunately, Redis doesn’t officially support Windows. If you’re on MacOS or Linux you’re fine. If you’re on Windows, you can look into using the version of Redis for Windows that Microsoft created, or you could use a cloud provider like Redis Labs and configure your application to use a remote database for sessions. Redis Labs gives you 30MB for free, which is enough for what we’re doing here.

For MacOS and Linux users, installing Redis is the same. Once you have Redis downloaded, Extract the archive and open the Redis directory in a terminal. Then just run the command make and Redis will begin to compile. Once it’s done, run the command make test just to make sure it worked. Lastly, to run Redis, run the command ./src/redis-server and leave the terminal open to keep Redis alive.

Back to our application. To store session data on Redis, we’ll use a package called connect-redis:

npm i -S connect-redis

Inside of app.js, require connect-redis below the session require statement and call it as a function passing in session as the only argument like so:

const RedisStore = require('connect-redis')(session);

Last, simply edit the session middleware to include a store option in the options object:

.use(session({
    store: new RedisStore(),
    secret: secrets.session_secret,
    resave: false,
    saveUninitialized: false
  }))

That’s it! Now if you run your server and log in with any user, their session data will persist even after the server has gone down and come back up. Restart the server and test that you are still logged in.

Code for this section: https://github.com/jayvolr/nodejs-auth/tree/6-Redis

Notes App

Alright, that’s pretty much it. That’s all the foundation you really need to create a basic web application that users can signup for, login to, and have their own unique information stored. To see exactly how to build an application on top of this, keep following along and I’ll quickly show you how to use everything we’ve done up to this point to create a simple note keeping app.

First things first, let’s create the view for the notes page called notes.hjs in the views directory:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Notes</title>
    <link rel="stylesheet" href="/css/master.css">
  </head>
  <body>
    <button id="logoutBtn">Logout</button>
    <div id="notesContainer">
      <h1>Your Notes</h1>
      <hr>
      <ul id="notesList">
        {{#notes}}
        <div class="noteItem">
          <li>{{note}}</li>
          <button class="deleteNote">delete</button>
        </div>
        {{/notes}}
      </ul>
      <form name="addNote" id="addNote" action="/addNote" method="post">
        <input id="noteTextBox" name="noteTextBox" type="text" name="note" placeholder="Add new note..." required>
        <input id="submitBtn" type="submit" value="Add">
      </form>
    </div>
  </body>
  <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
  <script src="/js/master.js" charset="utf-8" defer></script>
</html>

This page includes an unordered list that uses some of Hogan.js’s templating syntax to loop through a variable we’re going to provide called notes and display it. The page also includes a basic form pointing towards /addNote that the user will use to add new notes. Lastly, I’ve included a stylesheet, an AJAX library called axios, and script called master.js that’s going to handle carrying out the user’s actions and talking to the server.

master.css gist: https://gist.github.com/jayvolr/b76c479a6ceb51586614d1639a833b1f

master.js gist: https://gist.github.com/jayvolr/695a2ca00ad49c509021f647c90f8ff8

These files are placed inside the public directory, then inside css and js directories respectively like so:

myProject/
├── public/
│   ├── css/
│   │   ├── master.css
│   ├── js/
│   │   ├── master.js

In order for express to serve these files we need to assign the public directory the role of serving static files with some middleware. We can do that easily by adding the following line inside of app.js:

.use(express.static(__dirname + '/public'))

Next, to create the server logic for the app, create notes.js in the routes directory:

const express = require('express');
const router = express.Router();
const passport = require('passport');
const mongo = require('../db');
require('../passport');

router
  .post('/addNote', (req, res) => {
    mongo.db.collection('users').findOne({ email: req.user.email }, (err, user) => {
      let newNote = {
        posterEmail: user.email,
        note: req.body.note
      }
      mongo.db.collection('notes').insertOne(newNote, (err, result) => {
        if (err) throw err;
        res.sendStatus(200);
      });
    });
  })

  .delete('/deleteNote', (req, res) => {
    mongo.db.collection('notes').deleteOne({ posterEmail: req.user.email, note: req.body.noteToDelete }, (err, result) => {
      if (err) throw err;
      res.sendStatus(200);
    });
  })

  .get('/notes', (req, res) => {
    if (req.isAuthenticated()) {
      mongo.db.collection('notes').find({ posterEmail: req.user.email }).toArray((err, result) => {
        if (err) throw err;
        res.render('notes', { notes: result });
      });
    }else {
      res.redirect('/');
    }
  });


module.exports = router;

What this file does is:

The /addNote route stores the submitted note in the database in the notes collection along with the email of the user.

The /deleteNote route is supplied with the text of a note to delete, and deletes the note created by the current user and that matches the supplied note to delete.

The /notes route, assuming a user is logged in, simply searches for all notes belonging to the current user and renders notes.hjs passing in the notes variable to be used.

Next, inside of auth.js we can change the successRedirect option on the /login and /register routes to point to /notes. Now when a user logs in or registers, they’ll be shown their notes page.

Let’s also add a /logout post route. The notes.hjs file already has a button that will submit a request to this route when clicked.

.post('/logout', (req, res) => {
    req.session.destroy((err) => {
      res.sendStatus(200);
    });
  });

Also, we only want to display the login/register page if the user isn’t already logged in, so add the following logic to the / route in app.js to send the user to /notes if they’re logged in:

    if (req.isAuthenticated()) {
      res.redirect('/notes');
    }else {
      res.render('index');
    }

To use the routes defined in notes.js, we need to require and use them above .listen() inside of app.js:

.use(require('./routes/notes'))

If you want to add a bit of styling to the login/register page, edit index.hjs to look like this: https://gist.github.com/jayvolr/03611f4e3110e623f5f2b7cc3898294e

That’s all! The contents of the client-side JavaScript inside of master.js and how it works is beyond the scope of this article, but if you’re familiar enough with JavaScript, it’s relatively straightforward.

Finished product’s code: https://github.com/jayvolr/nodejs-auth/tree/7-Notes_App

If you were able to follow along and duplicate or create your own version of what I made, congratulations, that’s impressive! However, if this article assumed too much knowledge of you personally, I recommend looking for some video tutorials to see visually exactly how some things are done. Either way, thanks for reading! Feel free to leave a comment if you find a correction or if you need help.

Published inUncategorized