Node.js Session Management Using Express Sessions, Redis, and Passport - Part 1

jankleinert

Jan Kleinert

Posted on July 19, 2019

Node.js Session Management Using Express Sessions, Redis, and Passport - Part 1

Recently, I set out to create a demo application for my talk at Redis Day NYC that illustrates how session management works in a Node.js/Express web app, using Redis as the session store and then adds authentication on top of all that. Understanding the concepts and how they work together is one thing, but I hadn't actually built an app that used all these components together before.

As part of my initial research, I looked for existing tutorials or examples that did what I was trying to do. I found several good blog posts and tutorials, but none did exactly what I was looking for. Part 1 of this tutorial will take you step-by-step through the process of building a web app with Node.js and Express that uses express-session and connect-redis as a way of helping users understand how session management works. Part 2 will expand on this by implementing authentication using Passport and exploring how authentication and sessions work together.

Get the code for the craft beer name demo app

We will be starting with a simple demo app, and once we have that up and running, we'll add in session management and then authentication. Let's start by cloning the GitHub repo that has the code for the demo app and then switch to the beer-demo branch.

$ git clone https://github.com/jankleinert/redis-session-demo
$ cd redis-session-demo
$ git checkout beer-demo 
Enter fullscreen mode Exit fullscreen mode

Let's try running the app to make sure it works.

$ npm install
$ npm run dev
Enter fullscreen mode Exit fullscreen mode

Open http://localhost:3000 in your browser, and you should see something like this.

demo app screenshot

Understanding the demo app

The demo app was built using express-generator to create the app skeleton. It's using Pug for the view engine. When you click the Pour Another button, it makes a request to an API that will return a machine-learning-generated craft beer name. That's really all the app does at this point.

The three main files we'll be working with are app.js, /routes/index.js, and /views/index.pug.

dir structure

Why do we care about session management anyway?

"Session" is such an overloaded term, and can mean very different things depending on context. In this tutorial, we're talking about a user’s session in a web application. You can think of it as the set of requests and responses within a web app, initiated by a single user, from the start of their interaction until they end the session or it expires.

Why do we care about or need a construct like a session? HTTP is stateless, so each request and response pair is independent of the others. By default, no state is maintained and the server doesn’t know who you are from one request to another. Session management gives us the ability to assign an identifier to a user session, and use that ID to store state or data relevant to the session. This could be something like whether or not a user is authenticated, the items in a shopping cart, and so on - whatever state needs to be kept during that session.

There are multiple ways of handling session management, but we’re going to look at one specific way, where session data is kept in a session store, and we’ll be using Redis as the session store.

client and server

On the client side, a cookie is stored with the session ID but none of the session data. In your application’s session store (Redis in this case), the session ID is also stored, along with the session data.

Add a session info panel to the app

To make it easy to visualize what's happening with a session, we will add a session info panel to the app. Open /views/index.pug and add the following code to the bottom of the file. Be careful with your indentation; .session should line up in the same column as h1.

    .session
      p Session Info
      if sessionID
        p= 'Session ID: ' + sessionID 
      if sessionExpireTime
        p= 'Session expires in ' + Math.round(sessionExpireTime) + ' seconds'
      if beersViewed
        p= 'Beers viewed in this session: ' + beersViewed                           
Enter fullscreen mode Exit fullscreen mode

This panel will display the session ID, how many more seconds are left before the session expires, and also our session data: the number of beer names that have been viewed in this session. We'll be specifying those values in /routes/index.js in a later step.

Add express-session and connect-redis to app.js

express-session is session middleware for Express. It's pretty straightforward to set up and use. There are quite a few compatible session stores that you can use for storing session data. We will be using connect-redis. Let's start by installing the npm modules that we need.

$ npm install --save express-session uuid redis connect-redis                       
Enter fullscreen mode Exit fullscreen mode

Next, open up app.js and add the following code below the existing requires. uuid will be used to generate a unique ID to use for our session ID.

const uuid = require('uuid/v4')
const session = require('express-session');
const redis = require('redis');
const redisStore = require('connect-redis')(session);   

const redisClient = redis.createClient();

redisClient.on('error', (err) => {
  console.log('Redis error: ', err);
});
Enter fullscreen mode Exit fullscreen mode

Before we move on, make sure you have Redis installed and that the Redis server is running. If you need to install Redis, you can take a look at this documentation. Now we can set up the session middleware and tell it to use our Redis store as the session store. Add this code above the line app.use('/', indexRouter);.

app.use(session({
  genid: (req) => {
    return uuid()
  },
  store: new redisStore({ host: 'localhost', port: 6379, client: redisClient }),
  name: '_redisDemo', 
  secret: process.env.SESSION_SECRET,
  resave: false,
  cookie: { secure: false, maxAge: 60000 }, // Set to secure:false and expire in 1 minute for demo purposes
  saveUninitialized: true
}));                            
Enter fullscreen mode Exit fullscreen mode

There are a couple things to note about this code. The cookie that stores the session ID will be named "_redisDemo". We are using an environment variable to set the secret. In the next step, we'll export that env variable (you can set it to whatever you like). We are setting the session expiration to 1 minute to make it easier to understand what's happening in the demo app. In a real application, you'd set the maxAge to something more reasonable for your application. In your terminal, stop nodemon and then run the following.

$ export SESSION_SECRET=some_secret_value_here && npm run dev                   
Enter fullscreen mode Exit fullscreen mode

Add session management code to /routes/index.js

The last step will be to add logic to keep track of the number of beer names viewed per session and to pass the session-related information through to the session panel. Open /routes/index.js and replace the existing get and post with the code below.

router.get('/', function(req, res, next) {
  var expireTime = req.session.cookie.maxAge / 1000; 
  res.render('index', { sessionID: req.sessionID, sessionExpireTime: expireTime, beersViewed: req.session.views, beerName: null, beerStyle: null, error: null });
});

router.post('/', function (req, res) {
  request('https://www.craftbeernamegenerator.com/api/api.php?type=trained', function (err, response, body) {
    if (req.session.views) {
      req.session.views++
    } else {
      req.session.views = 1
    }
    var expireTime = req.session.cookie.maxAge / 1000;   

    if(err){
      res.render('index', { sessionID: req.sessionID, sessionExpireTime: expireTime, beersViewed: req.session.views, beerName: null, beerStyle: null, error: 'Error, please try again'});
    } else {
      var beerInfo = JSON.parse(body)

      if(beerInfo.status != 200){
        res.render('index', { sessionID: req.sessionID, sessionExpireTime: expireTime, beersViewed: req.session.views, beerName: null, beerStyle: null, error: 'Error, please try again'});
      } else {
        res.render('index', { sessionID: req.sessionID, sessionExpireTime: expireTime, beersViewed: req.session.views, beerName: beerInfo.data.name, beerStyle: beerInfo.data.style, error: null});
      }
    }
  });
});                         
Enter fullscreen mode Exit fullscreen mode

What did we change? In router.get, we added expireTime so that we can calculate the amount of time until the session expires. Then in res.render, we are passing some additional values: the session ID from req.sessionID, the expire time we just calculated, and the number of beers viewed per session, which is stored as req.session.views. On the first page view of a session, there will not be a value for req.session.views, but our template knows how to handle that.

In router.post, after we make the API request for the beer name, we are either incrementing req.session.views or setting it to 1 if this is the first beer name viewed in the session. Then, similar to what we saw above, we're passing along the additional session-related information in res.render.

Session management in action!

With everything in place now, open http://localhost:3000 in your browser. When it first loads, you should see the info panel displays a session ID and a time until the session expires.

Click on the Pour Another button (within 60 seconds, so your session doesn't expire), and you should see that the session ID remains the same, and now you also see the number of beers viewed in the session set to 1. If you open dev tools in your browser and view cookies, you should see a cookie named _redisDemo, and part of its value will contain the session ID.

session with cookie

Finally, if you start redis-cli and then issue the following command, where YOUR_SESSION_ID is replaced with the session ID shown in your browser, you should see the session data that's being stored in Redis for that session, including the views.

$ redis-cli
$ get "sess:YOUR_SESSION_ID"                            
Enter fullscreen mode Exit fullscreen mode

The output should look something like this:

redis-cli

Play around with the app some more to get a better understanding for how the sessions work. What happens if you close and then quickly re-open your browser? What happens if you wait more than 60 seconds and then refresh the page?

At this point, hopefully you have a better understanding of what session management is and how to implement it for a Node.js app using express-session and connect-redis. In Part 2, we'll build on what we've done in this tutorial by adding authentication to the app using Passport.

Just want the code from Part 1? Get it here:

GitHub logo jankleinert / redis-session-demo

Demo app that shows session management for a Node.js app using express-sessions and connect-redis

redis-session-demo overview

Demo app that shows session management for a Node.js app using express-sessions and connect-redis. Originally created for Redis Day NYC 2019: https://events.redislabs.com/sessions/life-user-session/

The app queries an API for ML-generated craft beer names and displays them on the page. There is a session management panel that displays session ID, time until the session expires, and the number of beer names viewed in that session.

Learn more about how it works in this tutorial:

how to run

Make sure you have have Redis server running locally:

redis-server

Then clone this repo, run npm install and then run it in dev mode:

git clone https://github.com/jankleinert/redis-session-demo
cd redis-session-demo
npm install
export SESSION_SECRET=<some value you choose>
npm run dev

Then in your browser, go to http://localhost:3000. It should look something like this:

screenshot

how it works

This demo uses express-session for session management and connect-redis as the session store.

branches

The master branch…




💖 💪 🙅 🚩
jankleinert
Jan Kleinert

Posted on July 19, 2019

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related