Best Practices for Secure Session Management in Node
Tags: node, express, javascript, security
Published: 2020-02-13 16:57:27

---
title: Best Practices for Secure Session Management in Node
published: true
description: In Web apps, sessions fulfill an important role between user authentication and authorization. Learn the best practices to secure sessions in your app.
tags: node, express, javascript, security
canonical_url: https://blog.jscrambler.com/best-practices-for-secure-session-management-in-node/
cover_image: https://blog.jscrambler.com/content/images/2020/02/jscrambler-blog-secure-session-management.jpg
---

In a web application, data is transferred from a browser to a server over HTTP. In modern applications, we use the HTTPS protocol, which is HTTP over TLS/SSL (secure connection), to transfer data securely.

Looking at common use cases, we often encounter situations where we need to retain user state and information. However, HTTP is a stateless protocol. Sessions are used to store user information between HTTP requests.

We can use sessions to store users' settings like when not authenticated. Post authentication sessions are used to identify authenticated users. Sessions fulfill an important role between user authentication and authorization.

## Exploring Sessions

Traditionally, sessions are identifiers sent from the server and stored on the client-side. On the next request, the client sends the session token to the server. Using the identifier, the server can associate a request with a user.

Session identifiers can be stored in cookies, localStorage, and sessionStorage. Session identifiers can be sent back to the server via cookies, URL params, hidden form fields or a custom header. Additionally, a server can accept session identifiers by multiple means. This is usually the case when a back-end is used for websites and mobile applications.

### Session Identifiers

A session identifier is a token stored on the client-side. Data associated with a session identifier lies on the server.

Generally speaking, a session identifier:

1. Must be random;
2. Should be stored in a cookie.

The recommended session ID must have a length of 128 bits or 16 bytes. A good pseudorandom number generator (PNRG) is recommended to generate entropy, usually 50% of ID length.

Cookies are ideal because they are sent with every request and can be secured easily. LocalStorage doesn't have an expiry attribute so it persists. On the other hand, SessionStorage doesn't persist across multiple tabs/windows and is cleared when a tab is closed. Extra client code is required to be written to handle LocalStorage / SessionStorage. Additionally, both are an API so, theoretically, they are vulnerable to [XSS](https://blog.jscrambler.com/exploring-the-owasp-top-10-by-exploiting-vulnerable-node-applications/#a7xsscrosssitescripting).

Usually, the communication between client and server should be over HTTPS. Session identifiers should not be shared among the protocols. Sessions should be refreshed if the request is redirected. Also, if the redirect is to HTTPS, the cookie should set after the redirect. In case multiple cookies are set, the back-end should verify all cookies. 

### Securing Cookie Attributes 

Cookies can be secured using the following attributes.

- The `Secure` attribute instructs the browser to set cookies over HTTPS only. This attribute prevents MITM attacks since the transfer is over TLS.
- The `HttpOnly` attribute blocks the ability to use the `document.cookie` object. This prevents XSS attacks from stealing the session identifier.
- The `SameSite` attribute blocks the ability to send a cookie in a cross-origin request. This provides limited protection against [CSRF attacks](https://blog.jscrambler.com/what-is-cross-site-request-forgery/?utm_source=dev.to&utm_medium=referral).
- Setting `Domain` & `Path` attributes can limit the exposure of a cookie. By default, `Domain` should not be set and `Path` should be restricted.
- `Expire` & `Max-Age` allow us to set the persistence of a cookie. 

Typically, a session library should be able to generate a unique session, refresh an existing session and revoke sessions. We will be exploring the `express-session` library ahead.

## Enforcing Best Practices Using express-session

In Node.js apps using Express, [express-session](https://github.com/expressjs/session) is the _de facto_ library for managing sessions. This library offers:

- Cookie-based Session Management.
- Multiple modules for managing session stores.
- An API to generate, regenerate, destroy and update sessions.
- Settings to secure cookies (Secure / HttpOnly / Expire /SameSite / Max Age / Expires /Domain / Path)

We can generate a session using the following command:

```
app.use(session({
  secret: 'veryimportantsecret',  
}))

```
The secret is used to sign the cookie using the [cookie-signature](https://www.npmjs.com/package/cookie-signature) library. Cookies are signed using [Hmac-sha256](https://blog.jscrambler.com/hashing-algorithms/?utm_source=dev.to&utm_medium=referral) and converted to a `base64` string. We can have multiple secrets as an array. The first secret will be used to sign the cookie. The rest will be used in verification.

```js
app.use(session({
  secret: ['veryimportantsecret','notsoimportantsecret','highlyprobablysecret'],
}))

```

To use a custom session ID generator, we can use the `genid` param. By default, [uid-safe](https://www.npmjs.com/package/uid-safe) is used to generate session IDs with a byte length of 24. It's recommended to stick to default implementation unless there is a specific requirement to harden `uuid`.

```
app.use(session({
    secret: 'veryimportantsecret', 
    genid: function(req) {
      return genuuid() // use UUIDs for session IDs
     }
}))
```

The default name of the cookie is `connect.sid`. We can change the name using the name `param`. It's advisable to change the name to avoid fingerprinting.

```
app.use(session({
  secret: ['veryimportantsecret','notsoimportantsecret','highlyprobablysecret'], 
  name: "secretname" 
}))
```

By default, the cookies are set to
```
{ path: '/', httpOnly: true, secure: false, maxAge: null }
```
To harden our session cookies, we can assign the following options:

```js
app.use(session({
  secret: ['veryimportantsecret','notsoimportantsecret','highlyprobablysecret'],  
   name: "secretname",
  cookie: {
      httpOnly: true,
      secure: true,
      sameSite: true,
      maxAge: 600000 // Time is in miliseconds
  }
}))
```

The caveats here are:

- `sameSite: true` blocks CORS requests on cookies. This will affect the workflow on API calls and mobile applications.
- `secure` requires HTTPS connections. Also, if the Node app is behind a proxy (like Nginx), we will have to set proxy to true, as shown below.

```
app.set('trust proxy', 1)
```

By default, the sessions are stored in `MemoryStore`. This is not recommended for production use. Instead, it's advisable to use alternative [session stores](https://www.npmjs.com/package/express-session#compatible-session-stores) for production. We have multiple options to store the data, like:

- Databases like MySQL, MongoDB. 
- Memory stores like `Redis`. 
- ORM libraries like `sequelize`.

We will be using Redis as an example here.
```
npm install redis connect-redis 
```


```js
const redis = require('redis');
const session = require('express-session');
let RedisStore = require('connect-redis')(session);
let redisClient = redis.createClient();

app.use(
  session({
    secret: ['veryimportantsecret','notsoimportantsecret','highlyprobablysecret'], 
     name: "secretname", 
     cookie: {
      httpOnly: true,
      secure: true,
      sameSite: true,
      maxAge: 600000 // Time is in miliseconds
  },
    store: new RedisStore({ client: redisClient ,ttl: 86400}),   
    resave: false
  })
)
```

The `ttl` (time to live) param is used to create an expiration date. If the `Expire` attribute is set on the cookie, it will override the `ttl`. By default, `ttl` is one day.

We have also set `resave` to false. This param forces the session to be saved to the session store. This param should be set after checking the store docs.

The `session` object is associated with all routes and can be accessed on all requests.

```js
router.get('/', function(req, res, next) {
  req.session.value = "somevalue";  
  res.render('index', { title: 'Express' });
});
```

Sessions should be regenerated after logins and privilege escalations. This prevents session fixation attacks. To regenerate a session, we will use:

```js
req.session.regenerate(function(err) {
  // will have a new session here
})
```

Sessions should be expired when the user logs out or times out. To destroy a session, we can use:

```js
req.session.destroy(function(err) {
  // cannot access session here
})
```

<hr>
<strong>Side Note</strong>: While this article focuses on back-end security, you should protect your front-end as well. See these tutorials on protecting <a href="https://blog.jscrambler.com/protecting-your-react-js-source-code-with-jscrambler/?utm_source=dev.to&utm_medium=referral">React</a>, <a href="https://blog.jscrambler.com/how-to-protect-angular-code-against-theft-and-reverse-engineering/?utm_source=dev.to&utm_medium=referral">Angular</a>, <a href="https://blog.jscrambler.com/how-to-protect-your-vue-js-application-with-jscrambler/?utm_source=dev.to&utm_medium=referral">Vue</a>, <a href="https://blog.jscrambler.com/how-to-protect-react-native-apps-with-jscrambler/?utm_source=dev.to&utm_medium=referral">React Native</a>, <a href="https://blog.jscrambler.com/protecting-hybrid-mobile-apps-with-ionic-and-jscrambler/?utm_source=dev.to&utm_medium=referral">Ionic</a>, and <a href="https://blog.jscrambler.com/protecting-your-nativescript-source-code-with-jscrambler/?utm_source=dev.to&utm_medium=referral">NativeScript</a>.
<hr>

## Extra Security with Helmet.js (Cache-Control)

Web Caching allows us to serve requests faster. Some sensitive data might be cached on the client computer. Even if we timeout the session, there might be a possibility that the data can be retrieved from the web cache. To prevent this, we need to disable cache.

From the POV of this article, we are interested in setting the  `Cache-Control` header to disable client-side caching. 

Helmet.js is an Express library that can be used to secure our Express apps.
The `noCache` method will set `Cache-Control`, `Surrogate-Control`, `Pragma`, and `Expires` HTTP headers for us. 

```js
const helmet = require('helmet')
app.use(helmet.noCache())
```

However, in general, it's wise to use the other options too. Helmet.js provides:

- `dnsPrefetchControl` to control browser DNS prefetching.
- `frameguard` to prevent clickjacking.
- `hidePoweredBy` to hide ` X-Powered-By` header.
- `hsts` for HTTP Strict transport Security
- `noSniff` to keep clients from sniffing MIME types
- `xssFilter` to add some XSS protection. 

Alternatively, if the site has the requirement to be cached, at the very least, the `Cache-Control` header must be set to `Cache-Control: no-cache="Set-Cookie, Set-Cookie2"`

```js
router.get('/', function(req, res, next) {
res.set('Cache-Control', "no-cache='Set-Cookie, Set-Cookie2'");
// Route Logic
})
```

## Logging Sessions

Whenever a new session is created, regenerated, or destroyed, it should be logged. Namely, activities like user-role escalation or financial transactions should be logged.

A typical log should contain the timestamp, client IP, resource requested, user ID, and session ID.

This will be helpful to detect session anomalies in case of an attack. We can use `winston`, `morgan` or `pino` to log these requests. By default, Express comes with `morgan` preinstalled. The default `combined` setting provides us standard Apache combined log output.

We can modify morgan to include session identifiers using custom morgan `tokens`. Depending on the use-case we add additional data to output. Similar processes can be implemented in other logging libraries.

```js
var express = require('express')
var morgan = require('morgan')

var app = express()

morgan.token('sessionid', function(req, res, param) {
    return req.sessionID;
});
morgan.token('user', function(req, res, param) {
    return req.session.user;
});

app.use(morgan(':remote-addr - :remote-user [:date[clf]] ":method :url HTTP/:http-version" :status :res[content-length] ":referrer" ":user-agent" :user :sessionid'))

app.get('/', function (req, res) {
  res.send('hello, world!')
})
```

Depending on the use case, logging scenarios should be built and implemented.

## Additional Client-Side Defenses 

There are some other client-side measures we can take to expire sessions.

### Session Timeouts on Browser Events

We can use JavaScript to detect if the `window.close` event is fired and subsequently force a session logout.

### Timeout Warnings

A user can be notified of session timeouts on the client-side. This will notify the user that his session is going to expire soon. This is helpful when a long business process is involved. Users can save their work before timeout OR continue working.

### Initial Login Timeout 

A client-side timeout can be set between the page that was loaded and the user that was authenticated. This is to prevent session fixation attacks, especially when the user is using a public/shared computer.

## Alternatives 

Currently, [JWT](https://blog.jscrambler.com/implementing-jwt-using-passport/?utm_source=dev.to&utm_medium=referral) is a viable alternative to the session. JWT is a stateless Auth mechanism. A `Bearer` token is sent in the header of every authenticated request. The payload of the JWT token contains the necessary details used for authorization. This is useful when we want to expose some part of our data as an API resource. However, unlike sessions, JWT is stateless and hence the logout code has to be implemented on the client-side. You can set an expiry timestamp in JWT payload but cannot force a logout.

## Final Thoughts

As we explored in this tutorial, managing sessions securely in Node/Express apps is a key security requirement.

We have highlighted some techniques to prevent some very serious attacks like CRSF, XSS, and others that might expose sensitive user information.
 
At a time when web-based attacks are growing fast, these threats must be addressed while developing the app to minimize the application’s attack surface.

---

For some further reading on security in JavaScript apps, check this <a href="https://jscrambler.com/code-integrity/javascript-security-data-sheet?utm_source=dev.to&utm_medium=referral">data sheet</a>.