{ Authentication with Express. }

Objectives:

By the end of this chapter, you should be able to:

  • Implement authentication and authorization with express
  • Use sessions to manage login and logout state
  • Use password hashing to store passwords properly

Putting it all together

We have learned how to hash passwords, compare passwords, and remember users as logged in by storing information in the session. Now it's time to think about how we can ensure that users are logged in and authorize only certain users to perform certain actions. For this, we will need to write some custom middleware that will intercept the request and either check to see if there is information in the session (authenticate) or compare information from the session and the url parameter (authorize) to ensure the user is allowed to reach a certain page or perform a certain action.

Authentication

Imagine we have a route that we only want authenticated (logged in) users to have access to. How could we prevent users who are not logged in from getting to the route below?

app.get('/welcome', function(req, res, next){
    res.send('You are logged in!');
})

Remember, that when a user is "logged in", there is information in the session that has marked that, so all we need to do is intercept the request using some middleware to see if the user is not logged in, and if they are not, we redirect them to the /users/login route. We can add our own middleware using app.use

// on every single request...
app.use(function(req, res, next){
    // if there is no value for the key of user_id in the session (null if we logged out or undefined if it has not been created yet)
    if(!req.session.user_id){
        res.redirect('/users/login');
    } else {
        next();
    }
})

The problem here is that now every route will be restricted to logged in users, which probably isn't what we want. So, how do we only use this middleware on certain routes? We would not want to stop someone from even getting to the signup page if they are not authenticated, so it's best to put this middleware in a function and only use it when necessary.

function loginRequired(req, res, next){
    if(!req.session.user_id){
        res.redirect('/users/login');
    } else {
        next();
    }
}

// now we can add that middleware....in the middle!
app.get('/welcome', loginRequired, function(req, res, next){
    res.send('You are logged in!');
})

You will also see commonly that these "helper" functions are placed in their own file and exported out to other files to be used.

Authorization

So we've successfully handled users who are not logged in, but what about protecting certain routes and making sure that only certain users have access to them (authorization)? In our sample route below, how could we stop a malicious user from deleting another user's post?

app.delete('/users/:user_id/post', loginRequired, function(req, res, next){
    res.send('A post was just deleted!');
})

We can write some custom middleware to do that as well!

function ensureCorrectUser(req, res, next){
    if(req.params.user_id !== req.session.user_id){
        req.flash('Not Authorized');
        res.redirect(`/users/${req.session.user_id}/posts`);
    } else {
        next();
    }
}

app.delete('/users/:user_id/post', loginRequired, ensureCorrectUser, function(req, res, next){
    res.send('A post was just deleted!');
})

Flash Messages

Very commonly when we redirect, it's very helpful to show the user a brief message ("Created", "Logged In Successfully", "Logged Out" etc.) and flash messages are a great way to do that. Since redirects involve two responses (one for sending a location and another for the next response), we need to store this message across multiple requests. Because of this, flash messages are stored in the session (which means we need to set up some session storage before using flash messages).

To use flash messages let's install the connect-flash module using npm install --save connect-flash and include it (make sure it is below where the session setup happens)

var flash = require('connect-flash');
var session = require('cookie-sessions');
app.use(session({secret: process.env.SECRET_KEY}));

app.use(flash());

// send flash messages to all routes
app.use(function(req, res, next){
    // res.locals is an object containing all the variables we can use in pug templates
    // let's add one called message, which will be any flash messages
    res.locals.message = req.flash('message');
    next();
});

We can now include the following in our base.pug to display the messages

<!DOCTYPE html>
html(lang="en")
head
    meta(charset="UTF-8")
    title Document
body
    if(message)
        p #{message}
    block content

Sample App

You can find a sample application with authentication and authorization using bcrypt, cookies, and sessions here.

When you're ready, move on to Authentication Exercises

Continue