WittCode💻

Node and Express - Sessions

By

Learn what sessions are, the express-session library, how sessions work with cookies to add persistence to an application, the difference between a session and a cookie, session security, what a session cookie is, and how to customize a session cookie.

Table of Contents 📖

What is a Session?

A session is a series of browser requests that come from the same client during a given time period.

Why do we need Sessions?

Websites use the HTTP protocol. HTTP is stateless, meaning that after each request and response the client and server forget everything about each other. If we want the client and server to remember one another, we can use a session. Sessions contain data about the client, allowing the server to keep track of the user.

Cookies vs Sessions

Both cookies and sessions are used for storing data across different page loads, but cookies are stored on the client while sessions are stored on the server. As such, sensitive information should not be stored on a cookie as anyone can access it. Instead, sensitive information should be stored on the server by using sessions.

How do Sessions Work?

When a client has successfully logged in, the server will create a session and store it. The server then responds to the client with a cookie that contains the session's unique id. This cookie is then sent with every request to the server. The server then looks at the session id in the cookie and looks up the saved session data, usually from a database.

Image

So once more, session data is stored on the server, but a unique id is stored on the client in a cookie. This id is then used by the server to get relevant information about the user. Therefore, sessions work with cookies in tandem.

Installing express-session

To use sessions with Express, we need to download the express-session library from npm.

npm install express-session

What is express-session?

The library express-session creates a session middleware to handle sessions in Express. express-session handles everything for us including creating the session, setting the session cookie, and creating the session object on the request object.

As sessions use a cookie to store an id, we need to create a cookie when we create a session. Before express-session version 1.5.0, the cookie-parser middleware would need to be used alongside express-session. However, in this article we have downloaded express-session version 1.17.2 which takes care of cookie parsing for us. So we don't need to make use of the cookie-parser middleware. So, remove the cookie-parser as global-middleware from our Express application. To do this, remove the code below from our main index.js file.

const cookieParser = require('cookie-parser');
app.use(cookieParser());

We can also uninstall it with npm by running the following.

npm uninstall cookie-parser

Registering expression-session as Global Middleware

Lets register express-session as global middleware. This will be done inside our main index.js file.

const session = require('express-session');
app.use(session());

Passing Options to express-session

The session function that we passed to app.use() takes an object as an argument. One option to pass is secret.

app.use(session({
    secret: 'thisismysecretdonttellanyone!'
}));

What is the Session Secret?

The session secret is used to create a hash to sign the session id cookie. This prevents the cookie from being tampered with. In other words, signing the session id cookie allows us to tell if the client modified the cookie. The session cookie set on the client will have the following format.

sid.signature

Sid is the session ID and signature is a signature generated by signing the sid with the secret provided to the express-session middleware. When the cookie is read, the signature is recalculated and compared to the signature attached to the cookie.

Session Secret Security

The secret provided to the session should be a random string of characters that is changed periodically. However, for demonstration purposes this will suffice.

Now that our session middleware is set up, we can see the session cookie if we start up our application. Run the Express application and check the application tab in the browser.

Image

We can customize the session cookie that is set on our browser. To do this, add a cookie property to the object provided to the session function.

app.use(session({
    secret: 'thisismysecretdonttellanyone!',
    cookie: {}
}));

Lets say we want to make it so the cookie is only sent along with requests in a first-party context. We can do this by setting SameSite to "strict".

app.use(session({
    secret: 'thisismysecretdonttellanyone!',
    cookie: { sameSite: 'strict' }
}));

Image

Changing our /login Route Middleware

The pug form that we made a few articles back has been adding users to our database. Lets change it to login users that are already registered instead. To do this, lets change the controller function that is called when a POST request is made to /login. Replace user.create with user.login inside routes/login/index.js.

router.post('/', user.login);

Creating a Login Method

Now, lets create a login method inside our user controller. Add the following to module.exports in controllers/user.js.

login: async (request, response) => {
}

What we want to do is use Sequelize to search for the username and password that were entered into the form. If those two match, then we send the user to their profile, if not we render the login form again.

if (request.body.username && request.body.password) {

    const { username, password } = request.body;

    let user = await User.findOne({
        where: { username, password }
    });

    if (user) {
        response.render('profile', { username });
    }
    else {
        response.render('login');
    }
}

What is findOne()?

The method findOne() is a Sequelize model method that searches for one row in the provided model using the provided arguments. Here, we are searching for a row in our user model where the username and password match the ones provided in the form. If the findOne method finds a row with the provided information it returns it. If not, it will return null.

Adding User Object to Session

Remember, the express-session middleware creates a session for us. We can access this session object in our middleware using the request.session object. Lets add our user object to the session object if the login was successful.

request.session.user = user;

Lets also tag on another property to our session object that tells our application the user has been authorized.

request.session.authorized = true;

Checking a User Already has a Session Set

If the user already has a session set with the authorized property, then lets lets skip the login page and send them right to their profile. Add the following to our /login GET route in routes/login/index.js

if (request.session.authorized) {
    response.render('profile', { username: request.session.user.username });
} else {
    response.render('login');
}

Now, if we visit the main page with an authenticated session, we will be directed to our profile page.

Deleting the Session at the /logout Route

By default, our session cookie will expire when the browser (not the tab) is closed. This is because the max-age/expires column is set to session, meaning expire when the browser closes. We can easily change this by setting the maxAge property of the cookie. However, there are times when we want to manually delete the session cookie, such as when the client logs out. To delete the session cookie we use the method request.session.destroy(). So, add the following to our routes/logout/index.js file.

router.get('/', (request, response) => {
    request.session.destroy();
    response.redirect('/login');
});

Now, when the user logs out of the application, the session cookie will be destroyed and they will be redirected to the login page.

Summary

But there we have it! If this article was helpful, please consider donating by using the link at the top of the page, sharing this article, and subscribing to my YouTube channel WittCode!