Express Sessions

Dr. Greg M. Bernstein

October 24th, 2019

Session Implementation

Why another library?

  • Sessions are a prime attack point for websites and web apps
  • A compromised session ID is as bad as a compromised password
  • Always good to use well proven libraries and techniques. “express-sessions” has over 600K weekly downloads.

References

express-session

Express Middleware to help us:

  • Manage session IDs: creation, deletion, validation
  • Associate session IDs with session storage
  • Sign and validate cookies used to transport session IDs

express-session

Uses other packages for:

  • Cryptographically strong session ID generation using uid-safe
  • Cookie signing/verification cookie-signature
  • Session Storage for production environments. See long list of adapters

Sessions

  • We’ll establish a session as soon as a user visits our site. Before login
  • We’ll alway keep track of the users login status or role, we can also keep track of some user information such as preferences or shopping carts prior to login if we like
  • After login we’ll make sure basic user information is available to the web server application.

Express-Session Function

  • If the session middleware sees an HTTP request without a cookie for a session ID it will generate one and allocate associated session storage.
  • If the middleware sees an HTTP request with a cookie for a valid session ID it will retrieve the stored session information and attach it to the express request parameter for easy use by our software.
  • The session middleware works with a session store of your choosing. It comes with a simple session store only suitable for development purposes.

Club Site Example (Template Based)

Example: Club Website

For all files get SessionExample.zip

Application specific access control!

  • Who can see club events: anyone
  • Who can see member list: only logged in members
  • Who can see basic club information: anyone
  • Who can add club events: only logged in members

Site Paths I

General GET paths:

  • ‘/’: Home path, basic site information, open to all
  • ‘/events’: Furnishes list of club events, open to all
  • ‘/users’: information about club member, only for members

Site Paths II

Login related paths, open to all:

  • ‘/loginForm’: returns a form to use for login
  • ‘/logon’: POST, checks user email and password and changes “logged in” status if verified
  • ‘/logout’: Used to logout and destroy the session

Site Paths III

Event adding only for members:

  • ‘/addEventForm’: returns the form for adding an event
  • ‘/addEvent’: Processes add event submission

Templates

Uses Nunjucks templates

  • base.njk: Contains HTML boilerplate and site menu. All other templates inherit from this.

  • Specific purpose templates: addEvent.njk, events.njk, goodbye.njk, loginError.njk, users.njk, Forbidden.njk, index.njk, logon.njk, welcome.njk

Base Template

Parent of all other templates, includes site navigation

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    {% block title %}
        <title>This is where the title goes</title>
    {% endblock %}
    <style>
        #Menu {
            display: flex;
            list-style: none;
            justify-content: space-around;
        }
        #CurUser {
            background-color: blue;
            color: white;
            font-family: Sans-Serif;
        }
    </style>
    {% block style %}
    {% endblock %}
</head>
<body>
<ul id="Menu">
    <li><a href="/">Home</a></li>
    <li><a href="events">Events</a></li>
    <li><a href="addEventForm">Add Event</a></li>
    <li><a href="users">Users</a></li>
    <li><a href="loginForm">Login</a></li>
    <li><a href="logout">Logout</a></li>
</ul>
{% if user and user.loggedin %}
    <p id="CurUser">User: {{ user.firstName }} {{ user.lastName }}</p>
{% endif %}
<main>
    {% block content %}
    {% endblock %}
</main>
</body>
</html>

Forbidden Template

Example specific use template

{% extends "base.njk" %}
{% block title %}
    <title>Forbidden!</title>
{% endblock %}
{% block style %}
    <style>
        body {margin: 1em;}
        main {background-color: #FCABA1;
            padding: 0.5em;
            font-family: sans-serif;}
        h1, h2 {text-align: center}
    </style>
{% endblock %}

{% block content %}
    <h1>Forbidden</h1>
    <h2>You must be logged in to perform this operation or see this information.</h2>
{% endblock %}

Initialization

Sets up cookie processing and session storage retrieval:

const session = require('express-session');
// change cookie name from default
const cookieName = "clubsid"; // Session ID cookie name, use this to delete cookies too.

// Create the session middleware and put it into general use
app.use(session({
  secret: 'website development CSUEB',
  resave: false,
  saveUninitialized: false,
  name: cookieName // Sets the name of the cookie used by the session middleware
}));

Initializing Session State

It is up to us to decide what goes into the session storage:

// This initializes session state
const setUpSessionMiddleware = function (req, res, next) {
  // We can attach any state/info we like to the session JS object
  // Below we add a user property.
  if (!req.session.user) { // Check for state or initialize it
    req.session.user = {loggedin: false};
  }
  next();
};

app.use(setUpSessionMiddleware); // Put it to use!

Initial Visit

Initial visit to homepage:

Protected Path Visit

Visiting /users prior to login:

Path Protection Middleware

We have a number of different paths to protect so we come up with some middleware:

// Use this middleware to restrict paths to only logged in users
const checkLoggedInMiddleware = function (req, res, next) {
  if (!req.session.user.loggedin) {
    res.render("Forbidden.njk");
  } else {
    next();
  }
};

Protecting Paths

Protecting paths with the middleware:

// Only available to logged in members
app.get('/users', checkLoggedInMiddleware, function (req, res) {
  res.render('users.njk', {users: users, user: req.session.user});
});

// Only available to logged in members
app.get('/addEventForm', checkLoggedInMiddleware, function (req, res) {
  res.render('addEvent.njk', {user: req.session.user});
});

// Other protected paths...

Logging In

Per OWASP recommendations we should generate a new session ID

app.post('/logon', express.urlencoded({extended:true}), function (req, res) {
    console.log(req.body);
    let email = req.body.email;
    let password = req.body.password;
    // Find user
    let auser = users.find(function (user) {
        return user.email === email
    });
    if (!auser) {// Not found
        res.render("loginError.njk");
        return;
    }
    let verified = bcrypt.compareSync(password, auser.passHash);
    if (verified) {
        // Upgrade in priveledge, should generate new session id
        // Save old session information if any, create a new session
        let oldInfo = req.session.user;
        req.session.regenerate(function (err) {
            if (err) {
                console.log(err);
            }
            req.session.user = Object.assign(oldInfo, auser, {
                loggedin: true
            });
            res.render("welcome.njk", {user: auser});
        });
    } else {
        res.render("loginError.njk");
    }
});

Prior to Login

Note that session ID has not changed.

After Login

Session ID changes, site knows user name:

Visiting Protected Path

Same session ID as after login

Logging Out

Removing the cookie on logout:

Logout Code

We destroy the session, and also show how to remove the cookie

app.get('/logout', function (req, res, next) {
  let options = req.session.cookie;
  req.session.destroy(function (err) {
    res.clearCookie(cookieName, options); // the cookie name and options
    res.render("goodbye.njk");
  })
});