Express Sessions (JSON)

Dr. Greg M. Bernstein

April 5th, 2020

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.

Tour Site Example (JSON API)

Tour Company

Example files in: SessionJSONExample.zip. Start of a tour company JSON API

  • Three user roles: guest, customer, admin
  • Only admins can add tours
  • All users can see tours, or login

APIs

  • /tours, GET: returns list of tours in JSON
  • /login, POST: takes email and password, returns user info
  • /logout, GET: ends the session
  • /addTour, POST: takes tour data, only admin users

Server Initialization

From tourServer.js

const express = require('express');
const app = express();
const session = require('express-session');
const bcrypt = require('bcryptjs');

const cookieName = "TourSid"; // Session ID cookie name, use this to delete cookies too.
app.use(session({
    secret: 'website development CSUEB',
    resave: false,
    saveUninitialized: false,
    name: cookieName // Sets the name of the cookie used by the session middleware
}));

// Fake user and tour data
const users = require('./secUsers.json');
const tours = require('./tours.json');

Session Initialization

From tourServer.js, uses middleware

// This initializes session state
const setUpSessionMiddleware = function (req, res, next) {
    console.log(`session object: ${JSON.stringify(req.session)}`);
    console.log(`session id: ${req.session.id}`);
    if (!req.session.user) {
        req.session.user = {role: "guest"};
    };
    next();
};

app.use(setUpSessionMiddleware);

Path Protection Middleware

From tourServer.js

// Use this middleware to restrict paths to only logged in users
const checkCustomerMiddleware = function (req, res, next) {
    if (req.session.user.role === "guest") {
        res.status(401).json({error: "Not permitted"});
        } else {
        console.log(`Session info: ${JSON.stringify(req.session)} \n`);
        next();
    }
};
// User this middlewave to restrict paths only to admins
const checkAdminMiddleware = function (req, res, next) {
    if (req.session.user.role !== "admin") {
        res.status(401).json({error: "Not permitted"});
    } else {
        next();
    }
};

A Protected Path

From tourServer.js

// Only available to admin, returns updated tour list.
app.post('/addTour', checkAdminMiddleware, express.json(), function (req, res) {
    let temp = req.body;
    console.log(temp);
    // Note need to check input here to prevent injection attacks
    let event = {
        name: temp.name,
        date: temp.date,
    };
    tours.virtTours.push(event);
    res.json(tours.virtTours);
});

Login Processing

From tourServer.js

// Available to all visitors, returns user info if successful
app.post('/login', express.json(), 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.status(401).json({error: true, message: "User/Password error"});
        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);}
            let newUserInfo = Object.assign(oldInfo, auser);
            delete newUserInfo.passHash;
            req.session.user = newUserInfo;
            res.json(newUserInfo);
        });
    } else {
        res.status(401).json({error: true, message: "User/Password error"});
    }
});

Testing Protected Interface

Using a Node.js program, addTourTest.js

/* Testing the POST /tours/add API */
const rp = require('request-promise-native');

let cookiejar = rp.jar(); // Use this to store cookies in between sessions.

let addTour = {
    uri: 'http://127.0.0.1:3434/addTour',
    json: true,
    method: "POST",
    body: {
        name: "Windsurf K2-18b, 110 Light Years",
        date: "Sometime in 2025"
    },
    jar: true
};

let loginOptions = {
    uri: 'http://127.0.0.1:3434/login',
    json: true,
    method: "POST",
    body: { // admin user, see users.json file
        "email": "antisun1921@outlook.com",
        "password": "R.r<E&xt"
    },
    jar: true
}

let loginCust = {
    uri: 'http://127.0.0.1:3434/login',
    json: true,
    method: "POST",
    body: { // admin user, see users.json file
        "email": "stedhorses1903@yahoo.com",
        "password": "nMQs)5Vi"
    },
    jar: true
}

async function someTests() {
    console.log("Try adding tour without logging in");
    try {
        let res1 = await rp(addTour);
        console.log(`Add Tour result: ${JSON.stringify(response)}`);
    } catch (e) {
        console.log(`Error: ${e}\n`);
    }

    console.log("Login as admin, then adding tour")
    try {
        let res2 = await rp(loginOptions);
        console.log(`login results: ${JSON.stringify(res2)}`);
        //console.log(`Cookie: ${JSON.stringify(cookiejar.cookies)}`);
        let res3 = await rp(addTour);
        console.log(`Add Tour result: ${JSON.stringify(res3)}\n`);
    } catch (e) {
        console.log(`Error: ${e}\n`);
    }

    console.log("Login as customer, then try adding tour")
    try {
        let res4 = await rp(loginCust);
        console.log(`login results: ${JSON.stringify(res4)}`);
        //console.log(`Cookie: ${JSON.stringify(cookiejar.cookies)}`);
        let res5 = await rp(addTour);
        console.log(`Add Tour result: ${JSON.stringify(res5)}\n`);
    } catch (e) {
        console.log(`Error: ${e}\n`);
    }
}

someTests();

Logout Processing

From tourServer.js

app.get('/logout', function (req, res) {
    let options = req.session.cookie;
    req.session.destroy(function (err) {
        if (err) {
            console.log(err);
        }
        res.clearCookie(cookieName, options); // the cookie name and options
        res.json({message: "Goodbye"});
    })
});