Simple User Authentication for Your Express Apps

If you are not building an extremely simple and static web site (like this one), you probably need your visitors to identify themselves somehow. This is called user authentication.

User authentication is one of the first things you stumble upon while building a new web app. It’s purpose is not only to identify different users but to allow them access to specific resources and functionality. It will also allow you to provide tailored experience for everyone individually. It should also be secure, so that no user can impersonate another.

All of this may sound complicated but there are many ways to implement user authentication easily while still keeping everything secure. With so many solutions to choose from you might be a little bit unsure which is the right way.

For example, companies like Google and Facebook allow you to identify the users with their accounts on Gmail and Facebook. It will save you time, but you will have very little for your users and you will be at the mercy of Facebook or Google if they change something. Another option is a solution from a company like Stormpath. They already have implemented user authentication and all you have to do it is use their API. Again the main disadvantage is less control that you will have.

Both type of solutions above are fine and sometimes are the best way to go. However, I will show you how to build your own user authentication from scratch. In addition of giving you more control, it will help you learn a few important concepts which then you can use even when you are using for example Facebook for authentication your customers.

Building your own user authentication doesn’t need to be complicated. You can build with just a few lines and then use it on every page that you need it. Actually, Express makes it very easy to have a nice, simple and secure user authentication. You need only two things and a few lines of code.

User Model

To authenticate users, your application should have a user model. It should provide at least methods for creation, removal and authentication. Let’s see an example of the model. We will use MongoDB to store all data.

// A helper file to connect to Mongo

var DB = require('../db')
  , crypto = require('crypto')
  , COLLECTION = 'users'

function hash(text) {
  return crypto.createHash('sha1')
  .update(text).digest('base64')
}

exports.create = function(name, email, password, cb) {
  var user = {
    name: name,
    email: email,
    password: hash(password)
  }

  var db = DB.getDB()
  db.collection(COLLECTION)
  .insert(user, function(err, docs) {
    if (err) return cb(err)
    cb(null, docs[0]._id)
  })
}

exports.all = function(cb) {
  var db = DB.getDB()
  db.collection(COLLECTION).find().toArray(cb)
}

exports.remove = function(id, cb) {
  var db = DB.getDB()
  db.collection(COLLECTION)
  .remove({_id:id}, function(err, docs) {
    cb(err)
  })
}

exports.get = function(id, cb) {
  var db = DB.getDB()
  db.collection(COLLECTION).findOne({_id:id}, cb)
}

exports.authenticate = function(email, password, cb) {
  var db = DB.getDB()
  db.collection(COLLECTION)
  .findOne({email:email}, function(err, doc) {
    if (err) return cb(err)

    if (doc.password === hash(password) {
      cb(null, doc)
    } else {
      cb()
    }
  })
}

You have four different methods. all, create and remove are pretty standard and clear what they do. Let’s look at authenticate. This will be the method which will use for authentication. It accepts an email and a password. If they there is a user with this email who has this password, the whole user is returned. Otherwise, nothing is returned.

This method can be made more specific so that we know whether the user doesn’t exist or whether the password is wrong. It can also be made to log any successful and unsuccessful attempts. Both of these are advanced functionality which we don’t need for our example, but you can explore later.

Sessions

Now that we have users, we need a way to remember when a user is logged in. There is a very useful express middleware which can help: express-session.

npm install express-session

By using this middleware you will have at your disposal a session object at every http request. If you put something in it, the next time the user makes a request you will still have the same session object with what you’ve already put inside. Best of all this data never gets to the user instead it is stored in your database.

Usually you will use the session to store something to identify your user and other helpful data that you may need at every request like for example the user id and the user name. However, you should only put those things in the session after the user has successfully logged in with their password. Moreover, when the user logs out you should remove everything from the session. Your sessions are also usually time limited, so that if the user doesn’t make any request for some extended period of time, a day or a week, the session object will automatically delete itself.

Authentication Middleware

Now that you have everything in place to build an authentication middleware which you can then use everywhere where user authentication is required. It is very simple and it only consists of three methods.

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

exports.login = function(req, res, next) {
  if (req.session.user) return res.redirect('/home')

  var email = req.body.email
  var pass = req.body.password

  User.authenticate(email, pass, function(err, user) {
    if (err) return next(err)

    if (user) {
      req.session.user = {id: user.id, name: user.name}
      res.redirect('/home')
    } else {
      res.redirect('/login')
    }
  })
}

exports.logout = function(req, res, next) {
  delete req.session.user
  res.redirect('/login')
}

// Authorize a given page only to registered users

exports.authorize = function(req, res, next) {
  if (req.session.user) {
    next()
  } else {
    res.redirect('/login')
  }
}

The first method can be used to log in a user, if he is not yet logged in. The second will log out any logged in user. The last method will redirect any request which is not from logged in user.

This is the simplest possible implementation. An advanced authorize method will usually accept an argument describing what kind of permissions the user must have to be allowed. For example, he might access freely some resources but for other he might need to be an administrator.

Putting everything to work

Using our authentication middleware is also very simple. Let’s see how it is done in the following snippet:

var express = require('express')
  , router = express.Router()
  , auth = require('../middlewares/auth')

// Post requests from a form can log in a user.

router.post('/login', auth.login)

// Get requests will log out the user.

router.get('/logout', auth.logout)

// This page is only visible to logged in users

router.get('/secret_page', auth.authorize, function(req, res) {
  res.render('secret_page_template', {user: req.user})
})

// This is available to everyone

router.get('/public_page', function(req, res) {
  res.render('public_page_template')
})

module.exports = router

All you have to do is to put auth.authorize before you provide the request handler. Now, all sensible routes are protected and you can provide user specific information when required. In addition, even on public pages you can use the req.session.user object to provide tailored experience to your users when logged in.

Next

We have discussed the most basic implementation of user authentication. Next you should take what you’ve learned here use it as a base for your needs and explore further. Try to add user permissions and store more useful user data in your sessions. You can also make your authenticate method to log authentication attempts and much more.

Did you like this article?

Please share it

We are Stefan Fidanov & Vasil Lyutskanov. We share actionable advice about development with Node, Express, React and other web & mobile technologies.

It is everything that we have learned from years of experience working with customers from all over the world on projects of all sizes.

Let's work together
© 2024 Terlici Ltd · Terms · Privacy