Skip to content

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

For me, learning to create my own user authentication functionality was a pivotal point in learning web development. It brought together so many things I had learned but hadn’t fully grasped how they might work together.

Building an authentication system involves using a web framework, a database, a hashing function, an interface, user input, middleware, and more. Assuming you’re not new to Node, you’re probably familiar with a lot of the pieces, but maybe you haven’t yet pulled them all together to create something meaningful. And really, handling authentication was a lot easier than I thought it would be, and it might be easier than you think, too.

Getting Started

The code created during this post is available on GitHub here. Each section is split into its own branch for convenience.

To get started, create a blank Node.js application or follow along as closely as possible inside an existing project. You can alternatively clone the 1-Getting_Started branch from this tutorials GitHub repo here: https://github.com/jayvolr/nodejs-auth/tree/1-Getting_Started

Basic Server

We’re going to be using the Express web framework for this project, so the next thing to do is to install express and save it to our dependencies using npm:

npm i -S express

Inside the application’s root directory, create a new directory called views. This is the default directory express will look in when we render a view. For my view engine I’m going to be using Hogan.js (hjs), but you can use whatever you’re familiar with.

npm i -S hjs

Next, create a home page in the views directory using whichever view engine you chose. I’m simply going to name mine index.hjs:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Node.js Auth</title>
  </head>
  <body>
    <h1>Hello auth</h1>
  </body>
</html>

Now, inside app.js, we can create a basic web server with express and listen for requests on port 3000:

// Application entry point
const express = require('express');

var app = express();

app
  .set('view engine', 'hjs')
  .get('/', (req, res) => {
    res.render('index');
  })
  .listen(3000, () => {
    console.log('Server listening on port 3000...');
  });

If you start the application by running node app you should be able to see your page being rendered when you visit localhost:3000.

Code for this section: https://github.com/jayvolr/nodejs-auth/tree/2-Basic_Server

Sessions

Now that we have a web server up and responding to requests, the next step is to set up sessions. A session is in essence how the application remembers who you are so that it can display information unique to you. When you log into any website where your experience is tailored to you (think Facebook or Twitter), you’ve created a session. Logging out or deleting your cookies ends that session.

To achieve this in express we’ll be using a module called express-session. First, we’ll need to install it:

npm i -S express-session

Now inside of app.js, require express-session:

const session = require('express-session');

Next we need to include the express-session middleware and pass through a few options:

.use(session({
   secret: 'little black dogs run very fast',
   resave: false,
   saveUninitialized: false
 }))

(This goes above .listen() and .get())

The secret is what is used when computing the hash for the session. To be secure, it should be a rather long string that isn’t easy to guess.

IMPORTANT: Before we move on, I’d be remiss if I didn’t note that it’s not a good idea to share this secret publicly. So, if you’re going to be hosting this code on GitHub or something like that, you should create a separate file to store your session secret and API keys and the like in. To do that, you can simply create a JavaScript file called secrets.js that exports an object containing everything you need in it. Then either move that file outside of your project’s root directory, or add it to your repository’s .gitignore file. Okay, back to it!

In order to see sessions in action, create two routes in express. One to set something to the session, and the other to view the session object in the browser:

  .get('/session', (req, res) => {
    res.send(req.session);
  })
  .get('/set', (req, res) => {
    req.session.name = "Jon";
    res.redirect('/session');
  })

Now if you start the server and point your browser to localhost:3000/set you should be redirected to /session where you can see the session object with the name property attached to it:

 

Note: I’m using a Chrome extension called JSONView to make JSON display nicely.

If you refresh /session or even navigate to another page and come back, you’ll notice the session persists. Now anywhere across the site, the application will remember my name is Jon (or whatever you put in). This is the behavior we’re after!

Although, if you restart your server all sessions are wiped, which is inconvenient and not suitable for production applications. The reason this happens is because, by default, express-session uses local memory to store session data. In the second part of this tutorial, we’ll look at persisting sessions through server restarts using Redis.

Code for this section: https://github.com/jayvolr/nodejs-auth/tree/3-Sessions

Authentication

Now that we have sessions setup, we can move on to authentication. Authentication is the act of authenticating—or verifying—a user’s identity within your application. In most web applications this involves asking the user for their username or email and their password.

Many times new developers may get confused between the two terms authentication and authorization. Though they both are important parts of security, they are almost entirely separate. In contrast to authentication, authorization is the act of determining what an already authenticated user is or isn’t allowed to do (such as accessing a page requiring special permissions, or deleting posts made by others). In this section we’re focused on authentication.

In this post we’re going to authenticate users by asking them for their email and password. So first, let’s create the interface that will allow the user to supply that information. Head over to your index file in the views directory and create a login form:

<h1>Login</h1>
<form id="loginForm" action="/login" method="post">
  <input type="text" name="username" placeholder="Email">
  <br><br>
  <input type="password" name="password" placeholder="Password">
  <br><br>
  <input type="submit" value="Login">
</form>

Note: The name attribute for the email or username input field must be username. This is what our authentication middleware expects by default unless explicitly changed.

Whenever this login form is submitted, the user’s browser is going to send an HTTP POST request to /login. In order for our application to read this request we need to use some middleware. The module we’ll use is body-parser:

npm i -S body-parser

Now require body-parser inside app.js:

const bodyParser = require('body-parser');

Finally, include the middleware:

  .use(bodyParser.json())
  .use(bodyParser.urlencoded({extended: false}))

To view the request created by our login form, create a new routes directory inside the root project directory and create a new file inside called auth.js:

const express = require('express');
const router = express.Router();

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

module.exports = router;

For this auth.js file to be able to respond to requests, we need to require it as middleware inside of app.js:

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

(This should go anywhere below the body-parser middleware, but .listen() should always go last.)

The body-parser middleware made the request body accessible via req.body. If you submit the login form you should see something like this:

 

The next step is to check the supplied credentials against a database, but first we need a database. For this we’re going to be using MongoDB. For the purposes of this post, I’ll be showing you how to setup your own database using MongoDB Atlas which has a free tier that will work just fine for what we’re doing.

First, setup a free account on the MongoDB site here: https://cloud.mongodb.com/links/registerForAtlas. Once you’re registered and authenticated (what normal people call logged in) navigate along until you’re presented with the option to Build a New Cluster:

You can choose whatever name you like for your cluster, I’m naming mine “development”. You can also choose whichever cloud provider you want, I’ll be sticking with the default option of AWS. And lastly, be sure to select the free option at the bottom, unless you’d like to pay for better servers. Click Confirm & Deploy!

The instance will take a little bit to spin up. While we’re waiting, click over to the Security tab and then down to IP Whitelist. Click the add button and then click Add current IP Address. You can add a comment like “Home computer” and then click Confirm. Now you will be able to connect to your database once it’s ready.

In order to make a connection to our database from within our application, we’re going to the official Node.js MongoDB driver called mongodb:

npm i -S mongodb

Next create a file in the root project directory called db.js that exports the database connection:

// MongoDB configuration
const MongoClient = require('mongodb').MongoClient;
const secrets = require('./secrets');

MongoClient.connect(`mongodb://jayvolr:${secrets.mongodb_password}@development-shard-00-00-hrtuw.mongodb.net:27017,development-shard-00-01-hrtuw.mongodb.net:27017,development-shard-00-02-hrtuw.mongodb.net:27017/development?ssl=true&replicaSet=development-shard-0&authSource=admin`, (err, connection) => {
  if (err) {console.warn('error from db.js'); console.log(err)}
  else {
    module.exports.db = connection;
    console.log('Connected to mongodb successfully.');
  }
});

(Be sure to either include your database password inside your secrets.js file or enter it manually into the connection string and remove the secrets.js require statement.)

The above attempts to connect to my own database, which obviously will not work for you, so you’ll need to replace the string passed to the connect method with the connection string for your own database. To obtain this string, return to your MongoDB Atlas clusters page and click the connect button. Then choose Connect Your Application and copy the connection string provided. You’ll need to replace the password and database placeholders with their respective values. I’m using a template literal so I can inject my password from my secrets.js file without having to concatenate.

Inside app.js, require the database connection like so:

const mongo = require('./db');

Now let’s add some data to our database. I’m going to be using some data generated by mockaroo, you can use the same data or generate your own. Store your data in an array inside app.js:

let mockData = [{
  "email": "nfrancescuccio0@ebay.com",
  "password": "ntXWndNWl"
}, {
  "email": "dgearty1@mapquest.com",
  "password": "T8pKPeC"
}, {
  "email": "brabidge2@desdev.cn",
  "password": "BpXNitqfB0s"
}, {
  "email": "gfolkerts3@examiner.com",
  "password": "Sf5fCVYYz"
}, {
  "email": "akrop4@house.gov",
  "password": "zua11BvS1PTF"
}, {
  "email": "asicha5@freewebs.com",
  "password": "RBquASfOXW5"
}, {
  "email": "rzannelli6@blinklist.com",
  "password": "wMOtvuY3Xc"
}, {
  "email": "gmattityahou7@uiuc.edu",
  "password": "8GHtyfoa2"
}];

To insert this data into the database, let’s repurpose the /set route inside app.js to insert the data:

.get('/set', (req, res) => {
    mongo.db.collection('users')
      .insertMany(mockData, (err, result) => {
        res.send(result.insertedCount + ' new document(s) entered');
      });
  })

Start your server and navigate to localhost:3000/set. This will create a users collection in your database and insert the mock data into it. Once the data is entered, remove the /set route from app.js completely to avoid inserting duplicate data. We can also remove the mockData variable.

Great! Now we have a database hooked up to our application and we have some user data inside of it. The next step is to make our login form actually log us in. To do this, we’re going to be using the passport.js authentication middleware. There’s two packages we’re going to need, passport and passport-local. Let’s install those now:

npm i -S passport passport-local

Require passport in app.js and then include its middleware:

const passport = require('passport');
  .use(passport.initialize())
  .use(passport.session())

Note: It’s important that these two pieces of middleware be placed below the sessions middleware.

Next let’s create the file for our passport logic. In the root project directory create a file called passport.js:

const LocalStrategy = require('passport-local').Strategy;
const mongo = require('./db');
const ObjectID = require('mongodb').ObjectID;
const passport = require('passport');

These are all the dependencies we’re going to need in this file. Next, we need to call passport.use() and pass in the strategy we want to use as well as a function to handle authentication requests:

passport.use(new LocalStrategy(authenticate));

We’re using the strategy provided by the passport-local module we installed. This strategy allows users to sign in with a username and password unique to our site. Other strategies include signing in with Google, Facebook, etc.

We passed the authenticate function to the local strategy, now we need to create that function:

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

This function is passed the username and password fields from our login form and a done function that takes two arguments: an error and a user object. The first thing we do when the authenticate function gets called is to go into that users collection in our database and call the findOne method on it, passing in the email as an object, and then providing a callback function with the err and user parameters. This will look inside our database for any documents with an email that match the one provided when a user submits the login form and pass the results to our callback.

Inside the callback we first check for an error, if one exists, we return the done function and pass in the error. Assuming there is no error, we check either if no user was returned—meaning no user in our database has that email—or if the password supplied does not match the password of the user with that email. If either of those conditions are true, we return the done function, passing in null as the error, false as the user and an object literal containing a message about why the user couldn’t be logged in.

If none of the previous two done functions are returned, that means the email and password matched a user in our database and we return the done function with no error and the user as the second argument.

Finally, we need to supply a serializeUser and deserializeUser function:

passport.serializeUser(function(user, done) {
  done(null, user._id);
});

passport.deserializeUser(function(id, done) {
  mongo.db.collection('users')
    .findOne({ _id: new ObjectID(id) }, (err, user) => {
      done(err, user);
    });
});

The purpose of these functions is so that passport can set the user on the session. The serializeUser function is where we decide what information will be stored on the session. Since the unique id generated by MongoDB is enough to identify the user, that’s all we will store on the session.

The deserializeUser function takes what we stored in the session and returns the full user object. This deserialized user will be available inside req.user. In order to get the user in the database from their id, we need to create a new ObjectID from it because this is what ids are stored as by default in MongoDB.

Ok, our passport.js file is complete, now we just need to forward the request submitted by our login form over to passport. To do this, open up the auth.js file and require the passport module and our passport file:

const passport = require('passport');
require('../passport');

Then, replace the /login POST route with this:

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

Passport will now use the local strategy we defined whenever a user submits the login form. You should now be able to log in using a username and password from the mock data and see a user id attached to the session!

Now anywhere across your site the logged in user will be available under req.user. Passport also includes a method for determining if a user is authenticated: req.isAuthenticated(). This will return true or false depending on if there is a user logged in.

Code for this section: https://github.com/jayvolr/nodejs-auth/tree/4-Authentication

That’s it for this post and the first part of this series. In part 2 I’ll cover how to setup logging out and registration, how to properly secure passwords—you don’t want to be storing them in plain text like we are currently, it’s not secure at all—how to setup Redis for sessions so that sessions persist even when the server restarts, and we’ll also setup a notes page where logged in users can create and delete personal notes.

Part 2: https://jayvolr.me/2017/09/18/user-authentication-in-node-js-part-2-of-2/

Published inUncategorized