Node.js Session Management Using Express Sessions, Redis, and Passport - Part 2
Tags: redis, express, session, passport
Published: 2019-07-19 15:37:14

In [Part 1](https://dev.to/jankleinert/node-js-session-management-using-express-sessions-redis-and-passport-part-1-cja) of this tutorial, we went step-by-step through the process of building a web app with Node.js and Express that uses [express-session](https://github.com/expressjs/session) and [connect-redis](https://github.com/tj/connect-redis) as a way of helping users understand how session management works.

In this second part, we will expand on the previous app by implementing authentication using [Passport](http://www.passportjs.org/) and exploring how authentication and sessions work together.

### Pre-requisites

If you followed the steps in [Part 1](https://dev.to/jankleinert/node-js-session-management-using-express-sessions-redis-and-passport-part-1-cja), then you can move on to the next section. If not, here's what you need to do.

Clone this GitHub repo that has the code for the demo app. The master branch contains the code as it is at the end of Part 1. You'll also need to install Redis and start the Redis server if you don't already have it installed. If you need to install Redis, you can take a look at [this documentation](https://redis.io/topics/quickstart).

```
$ git clone https://github.com/jankleinert/redis-session-demo
$ cd redis-session-demo
``` 
            

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

```
$ npm install
$ export SESSION_SECRET=some_secret_value_here
$ npm run dev
``` 
            

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

![demo app screenshot](https://jankleinert.com/assets/images/blog/node-redis-session-id-first-load.png)

### Set up a MySQL user database

Regardless of whether or not you completed Part 1, you'll need to ensure you have MySQL installed. [Instructions are here](https://dev.mysql.com/doc/refman/8.0/en/installing.html) if you need to install and set up MySQL. Next, launch `mysql` and create a new database and a new table.

```
mysql> CREATE DATABASE redis_session_demo; 
mysql> USE redis_session_demo;
mysql> CREATE TABLE users (id varchar(20), email varchar(20), password varchar(60));
``` 
            

This will be our user database. To speed things up, rather than having an account creation page, we'll manually insert a test user into the database. The app will be using `bcrypt` to create a hash for our passwords. Our test user will have `id = a1b2c3d4`, `email = test@example.com`, and `password = password`. You can use [this site](https://bcrypt-generator.com/) to create a hashed password. Next, we'll insert that into our user database.

```    
mysql> INSERT INTO users (id, email, password) VALUES ('a1b2c3e4', 'test@example.com', '$2y$12$7Mj1fG3bdlpmRcXtZpwimOI4pItCQcj5x2.ZqydPbR5wWlKGVaQVe');               
```                

### Quick recap of the demo app

The demo app was built using [express-generator](https://expressjs.com/en/starter/generator.html) to create the app skeleton. It's using [Pug](https://pugjs.org/api/getting-started.html) for the view engine. When you click the Pour Another button, it makes a request to an [API](https://www.craftbeernamegenerator.com/about.html) that will return a machine-learning-generated craft beer name.

In Part 1, we added a session information panel that displays 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 the current session. To implement session management, we used [express-session](https://github.com/expressjs/session) for the session middleware and [connect-redis](https://github.com/tj/connect-redis) as the session store. In the next step, we will add links to log in and log out, create a login page, and refactor the session panel that was originally included directly in `/views/index.pug`.

### Add authentication support to the frontend

We are going to start by refactoring the session info panel. By moving it to a separate file, it will be easier to include it in multiple pages. Create a new file `/views/session.pug` and paste in this code. There is a section at the bottom now that displays whether or not the user is authenticated.

```    
    .session
      p Session Info
      if sessionID
        p= 'Session ID: ' + sessionID 
      if sessionExpireTime
        p= 'Session expires in ' + Math.round(sessionExpireTime/1000) + ' seconds'
      if beersViewed
        p= 'Beers viewed in this session: ' + beersViewed
      else 
        p= 'No beers viewed yet in this session.'
      if isAuthenticated
        p= 'Logged in as: ' + email
      else  
        p= 'Not logged in'						
```                

Now, open up `/views/index.pug` and replace the `.session` section with the following line. It should be lined up in the same column as the `h1`.

```    
    include session.pug              
```                

It's time to create the login page. Create `/views/login.pug` and paste in this code. It's a simple form with fields for email and password.

```    
    extends layout
    
    block content
      h1= 'Log In'
      .lead
        form#login-form(action='/login', method='post')
          .form-group
            input(name='email', type='text', placeholder='Email', required='')
          .form-group
            input(name='password', type='password', placeholder='Password', required='')
          button.btn.btn-primary(type='submit')= 'Log In'
        if error
          p= error
      
      include session.pug              
```                

Now, we need to add Home, Log In, and Log Out links to the navigation. Open `layout.pug` and add this code directly below `h3.masthead-brand Craft Beer Name Demo`.

```    
    nav.nav.nav-masthead.justify-content-center
      a.nav-link(href='/') Home
      a.nav-link(href='/login') Log In
      a.nav-link(href='/logout') Log Out
```                

### Update app.js

To support adding authentication to the app, we need to install some additional packages.

```    
npm install --save bcryptjs mysql passport passport-local						
```                

`bcryptjs` is used for for hashing and checking passwords. [`passport`](http://www.passportjs.org/) is the authentication middleware we are using, and [`passport-local`](http://www.passportjs.org/packages/passport-local/) is the authentication strategy, meaning we are authenticating with a username and password.

Next, open up `app.js` and add the following code below the existing `require`s.

```    
const loginRouter = require('./routes/login');
const logoutRouter = require('./routes/logout');
const mysql = require('mysql');
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
const bcrypt = require('bcryptjs');
    
const mysqlConnection = mysql.createConnection({
  host: 'localhost',
  user: 'root',
  password: '', // demo purposes only
  database: 'redis_session_demo'
});
      
mysqlConnection.connect(function(err) {
  if (err) {
    console.log('error connecting to mysql: ' + err.stack);
    return;
  }
});
```                

Note that we're using a MySQL database with `root` and `''` as the login and password. This is only for demo purposes; don't do that in production! You probably also noticed `loginRouter` and `logoutRouter` reference files that don't exist. We'll create those in the next section.

Scroll down a bit until you see `const redisClient = redis.createClient();`. Directly after that line, add the following code.

```    
// configure passport.js to use the local strategy
passport.use(new LocalStrategy(
  { usernameField: 'email' },
  (email, password, done) => {
    mysqlConnection.query('SELECT * FROM users WHERE email = ?', [email], function (error, results, fields) {
      if (error) throw error;
      var user = results[0];
      if (!user) {
        return done(null, false, { message: 'Invalid credentials.\n' });
      }
      if (!bcrypt.compareSync(password, user.password)) {
        return done(null, false, { message: 'Invalid credentials.\n' });
      }
      return done(null, user);
    });
  }
));
    
passport.serializeUser((user, done) => {
  done(null, user.id);
});
    
passport.deserializeUser((id, done) => {
  mysqlConnection.query('SELECT * FROM users WHERE id = ?', [id], function (error, results, fields) {
    if (error) {
      done(error, false);
    }
    done(null, results[0]);  
  });
});              
```                

I found [this article](https://medium.com/@evangow/server-authentication-basics-express-sessions-passport-and-curl-359b7456003d) very helpful in understanding what happens during the authentication process. You'll notice that I modeled some parts of this code after what was done in that article.

Scroll down a bit more in the file until you find `app.use('/', indexRouter);`. Replace that line with the following code.

```    
app.use(passport.initialize());
app.use(passport.session());
    
app.use('/', indexRouter);
app.use('/login', loginRouter);
app.use('/logout', logoutRouter);						
```                

This code is setting up our app to use passport as middleware, and then we're adding the two new routes for logging in and logging out.

### Update routes

The last step we need to take is to update `/routes/index.js` and create two new files: `/routes/login.js` and `/routes/logout.js`. Open `/routes/index.js`. In each of the four `res.render()` calls, add this to the end of the list of properties in the locals object.

```    
, isAuthenticated: req.isAuthenticated(), email: (req.isAuthenticated() ? req.user.email : null)              
```                

So, for example, the `res.render()` call in `router.get()` would become:

```    
res.render('index', { sessionID: req.sessionID, sessionExpireTime: expireTime, beersViewed: req.session.views, beerName: null, beerStyle: null, error: null, isAuthenticated: req.isAuthenticated(), email: (req.isAuthenticated() ? req.user.email : null) });
```                

Next create `/routes/login.js` and paste in this code.

```    
const express = require('express');
const router = express.Router();
const passport = require('passport');
    
/* GET request for login page */
router.get('/', function(req, res, next) {
  var expireTime = new Date(req.session.cookie.expires) - new Date();
  res.render('login', { sessionID: req.sessionID, sessionExpireTime: expireTime, beersViewed: req.session.views, error: null, isAuthenticated: req.isAuthenticated(), email: (req.isAuthenticated() ? req.user.email : null) });
});
    
    
router.post('/', function (req, res, next) {
  var expireTime = new Date(req.session.cookie.expires) - new Date();
  passport.authenticate('local', (err, user, info) => {
    if(info) {return res.send(info.message)}
    if (err) { return next(err); }
    if (!user) { return res.redirect('/login'); }
    req.login(user, (err) => {
      if (err) { return next(err); }
      res.render('login', {sessionID: req.sessionID, sessionExpireTime: expireTime, beersViewed: req.session.views, username: req.user.id, error: null, isAuthenticated: req.isAuthenticated(), email: (req.isAuthenticated() ? req.user.email : null)});
    })
  })(req, res, next);
})  
    
router.get('')
    
module.exports = router;              
```                

`router.post()` is where our login form submissions are handled using passport, using the local strategy.

Finally create `/routes/logout.js` and paste in this code.

```    
const express = require('express');
const router = express.Router();
    
/* GET request for logout page */
router.get('/', function(req, res, next) {
  var expireTime = new Date(req.session.cookie.expires) - new Date();   
  req.logout();
  req.session.destroy(function() {
    res.redirect('/');
  });
});
    
router.get('')
    
module.exports = router;              
```                

There is no page that is displayed specifically for logging out. Instead, when the `GET` request is made to `/logout`, the app will log the user out, destroy the session, and then redirect them to the home page. At this point there will not be an authenticated user, and a new session will be created.

### Try it!

Let's try it out! Open [http://localhost:3000](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, as well as "Not logged in".

![add auth](https://jankleinert.com/assets/images/blog/node-redis-add-auth.png)

Click the "Log In" link in the header and authenticate using our test credentials: `test@example.com` / `password`. When the page reloads, you should see that you are now logged in as test@example.com

![user logged in](https://jankleinert.com/assets/images/blog/node-redis-user-logged-in.png)

As you take other actions on the site, you'll see that you stay logged in, but if you click "Log Out" in the navigation, you will no longer be logged in and a new session will be started.

![user logged out](https://jankleinert.com/assets/images/blog/node-redis-logged-out.png)

That's it! You now have a simple app that handles session management as well as authentication. Is it complete? Definitely not! There are lots of improvements and additions that could be made, including an account creation page, better error handling, etc. You can find the complete code for Part 2 [on GitHub](https://github.com/jankleinert/redis-session-demo/tree/auth).