Session handling is a globally used concept, without which any website or app, regardless of whether it is written in Node.js, PHP or any other backend language, will be in a precarious condition. It enables the user information to be persistent in a ‘stateful’ manner across all the pages of that website or app. In this article, we shall use Node.js to create and manage sessions.
Let us first look at how a standard HTTP request works (without sessions) and what are its drawbacks. Then we shall discuss what a session is, and how it overcomes all the drawbacks and adds to the security of a client-server connection.
The Hypertext Transfer Protocol
The classic Hypertext Transfer Protocol (HTTP) is a stateless tool. This means every request that is sent from a single client is interpreted by the Web server independently and is not related to any other request. There is no inbuilt mechanism for the server to remember a specific user from different multiple requests, which also makes it impossible for the server to know if each request originated from the same user.
In this model, if the server is required to display some user-specific information, the client has to be authenticated with each and every request. Imagine messaging on Facebook and typing in your user name, email and password every single time you send a new message!
The session solution
To overcome this problem, we need to introduce what is called a state-management protocol for websites and apps. A stateful connection is one that enables the server to realise which requests originate from the same user. This is handled by implementing what is globally known as a session.
State management flow with sessions and cookies
Now that we are using a state-management tool, let’s look at the flow of data when a client tries to log in using authentic credentials from the server:
- The client visits the login page of a website and fills out the login form using the user name, email and password.
- After submitting the form, the credentials are validated at the server end and the request is authenticated.
- If the entered credentials are valid, the server generates a unique random number, known as the session ID, which is also stored on the server in a specific folder in which other session-specific information is stored.
- The session ID is sent back to the user in the cookie header of the response data. For Node.js, this cookie header is named connect.sid.
- On the client side, if cookies are enabled by the user on the browser, they are saved in the memory space set aside by the user exclusively for cookies.
- For each request that goes to the Web server from this client, the connect.sid cookie is passed back in the header file. On coming across this, the server tries to initialise a session with that particular session ID. This is done by loading the session file that was created earlier when the session was being initialised. Then, a global array variable (request in case of Node.js) is initialised with the data stored in this session file.
Thus, this authentication data is preserved with all sequential incoming requests and the user is kept logged in throughout the session until he or she manually logs off.
Let’s look at a working example. Now that we’ve seen how important it is to have sessions, let’s build a mini Web application using Node.js, with Express as a framework and a few other modules from the Node Package Manager.
Note: This article assumes that the reader has basic Node.js and jQuery knowledge, apart from also having worked with npm on the command line and locally hosted a Node.js app.
To demonstrate session management, we will use a basic log-in/log-out system where the user logs in using a user name, email and password and the session is linked to the user via the provided user name and email. Upon logging out, the session is destroyed and the user will be redirected to the home page, where he or she can log in again.
Setting up the Node.js project
Create a new folder and open a terminal in that directory.
Note: Here, we are using the Windows command line tool.
npm init --y
This creates the package.json file for building the Node project. Let’s now install all the required dependencies:
npm install --save express body-parser express-session
Now that the dependencies are installed, let us start coding the main app with routes logic.
We will use a module called express-session, which acts as the middleware for internally handling our sessions. This also requires that we use Express in our project.
const express = require(‘express’); const session = require(‘express-session’); const bodyParser = require(‘body-parser’);
We initialise the session as follows:
app.use( session({ secret: ‘thisisasecret’, saveUninitialized: false, resave: false }) );
Here, the secret is the hash key passed to the header file so that this information can be securely decoded at the server end only. This is important because without a session secret, any third party app can easily look into the cookie and hijack it. Having a secret ensures that when authenticating, the cookie data is encoded using the secret key only known to the client and the server; this data can then not be easily decoded by a third listener.
Using the request variable, the session can be assigned to any variable for reading and manipulation.
var sess; app.get(‘/’, function(req, res) { sess = req.session; /*
The session is assigned to the variable sess here. And it can now be manipulated by creating and assigning new session variables.
*/ //sample session variables sess.email; sess.username; });
Note: In this sample code, we are creating a session variable ‘sess’, and using the email and the user name as two attributes. In the final code at the end, we will use different variables (email and visits) to show the working of the session.
Project structure
The server code will be in index.js, and home.html will be the main landing page where the login form will be displayed.
Here is the server-side code in index.js:
//index.js //including the imported packages const express = require(‘express’); const session = require(‘express-session’); const bodyParser = require(‘body-parser’); //initialize the app as an express app const app = express(); const router = express.Router(); //middleware app.use( session({ secret: ‘thisisasecret’, saveUninitialized: false, resave: false }) ); app.use(bodyParser.json()); app.use( bodyParser.urlencoded({ extended: true }) ); var sess; //is a global variable, NOT RECOMMENDED! //ROUTERS //Router 1: for redering the hompage router.get(‘/’, (req, res) => { sess = req.session; if (sess.email && sess.visits) { return res.redirect(‘/admin’); } else { sess.visits = 1; res.sendFile(__dirname + ‘/home.html’); } }); //Router 2: for login operation router.post(‘/login’, (req, res) => { sess = req.session; sess.email = req.body.email; res.end(‘done’); }); // Router 2: goes to a page that can only be accessed // if the user is logged in. router.get(‘/admin’, (req, res) => { sess = req.session; if (sess.email) { res.write(`<h1>Hello ${sess.email} </h1><br>`); res.write( `<h3>This is visit number ${sess.visits}</h3><h5>Refresh page to increase visits</h5>` ); sess.visits++; res.end(‘<a href=’ + ‘/logout’ + ‘>Click here to log out</a>’); } else { res.write(‘<h1>You need to log in before you can see this page.</h1>’); res.end(‘<a href=’ + ‘/’ + ‘>Login</a>’); } }); //Router 4: for session destruction router.get(‘/logout’, (req, res) => { req.session.destroy(err => { if (err) { return console.log(err); } res.redirect(‘/’); }); }); app.use(‘/’, router); app.listen(process.env.PORT || 3000, () => { console.log(`App Started on PORT ${process.env.PORT || 3000}`); });
Each router first checks if the variable user name is set in the session that can only be set by logging in through the front-end.
This will be our HTML template for the front-end, and will be used for logging the client in and out. It is a very plain and basic HTML template without any CSS, and this is only being used for demo purposes.
<html> <head> <title>Testing Session</title> </head> <body> Username: <input type=”text” id=”email” /><br /> Password : <input type=”password” id=”password” /><br /> <input type=”button” value=”Submit” id=”submit” /> </body> <script src=”http://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js”></script> <script> jQuery(document).ready(function($) { var email, pass; $(‘#submit’).click(function() { email = $(‘#email’).val(); pass = $(‘#password’).val(); $.post(‘/login’, { email: email, pass: pass }, function(data) { if (data === ‘done’) { window.location.href = ‘/admin’; } }); }); }); </script> </html>
Note: The form inputs (email and password) are not being checked for any sort of validation. The server is simply accepting whatever values the client inputs, as this is a simple demonstration for session creation.
Halt! You have a bug!
Take a moment and look at the above code snippet that we created with the session variable sess. What’s wrong with it? Did you get it? No?
Well, this variable is a global one. And as we all know, global variables can prove to be terrible security holes in production level projects.
Here, since sess is global, the session won’t work for multiple users as the server will create the same session for all the users. This can be solved by using what is called a session store.
We have to store every session in the store so that each one will belong to only a single user. One popular session store is built using the Redis module. Session stores can also be created at the server using the already implemented (if any) database management systems like MySQL, MongoDB, PostgreSQl, etc. More information about this can be found on the official express-session npm website https://www.npmjs.com/package/express-session.
Let’s run the code!
To run the app, simply go to the main directory of the Node project and use the following command:
npm index.js
This should give you a message that says:
App Started on PORT 3000 (or any other free port)
To see exactly what is stored in the cookie, let us use console.log() as follows:
console.log(req.session);
Once the server is running, the home page will look like what’s shown in Figure 4.
Here, I have used testusername and password as test case inputs.
Once the user logs in from the form in Figure 4, the page is redirected to what’s shown in Figure 5.
Figure 6a shows what we have on the console.
You can see the number of visits made by the client in Figure 6a. When the page is refreshed, the browser essentially sends a GET request back to the server side with the email variable from the cookie on the client machine. The server reads this and realises that this user is logged in as testusername, and thus the variable session.visits is only updated for the current logged in session and not globally as each and every request.
Figure 6b shows what the page looks like after a few refreshes.
Figure 7 shows what we have on the console.
As you can see in Figure 7, the visits variable has become 3, and that’s what it displays on the page as seen in Figure 6b.
Let us now test what the website does when we try visiting http://localhost:3000/admin without being logged in (Figure 8).
Figure 9 shows what we have on the console now.
As we can see in Figure 9, there is no email variable in the cookie being sent, and thus no user is logged in this time; so the website prompts the user to the login page with the link.
In this tutorial, we have successfully managed to validate user sessions with a simple login page by using Express’ simple and effective implementation of session management. Hopefully, you can follow along and recreate the same output with your own forms and validations.