Loading...
Before we even talk about what are sessions lets describe a use case for web applications. Lets say we are building a todo web application to list our tasks. Every user has its own list of tasks and every user can only see his tasks. We place a page in our application to authenticate the user with a username and a password in the route /login after the user is logged in our app has a /tasks route to list the user tasks, as well as a /settings route for the application settings. Only logged in users can view the tasks and settings routes. Our user loggs in once and he can access those routes, meaning we need somehow a way to know when a user requests the tasks and settings routes that the user logged in before and what is the current user. Being able to turn request and response which are basically stateless and turn them to statefull meanning knowing that the user logged in before and what is the user requesting the page, is exactly the reason why we have sessions.
Sessions are a way of storing data which is unique per user, and persistent for that user. Which means that user A will have a data associated for him through out all his requests to our server. User B will be have data associated to user B and will not be able to access the data of user A only the data associated to him.
As stated above user A has certain data as well as user B. our server needs to know if the request we are currently handeling belong to user A or user B. When the browser sends the request the server gets the request and we can decide in the server if we want to add session data to that request. If we set session data our server needs to save that data (we will later specify how that data can be saved) and the server creates an identifier for that data. As an example for user A we are saving data under the identifier numbered 1, and for user B we are saving different data under the identifier 2. The server will send a response to the client browser with a cookie, and in the cookie we will specify the identifier. Browsers when sending requests will automatically send the cookies to the server, so the next request our server will get the identifier and will know which is the user requesting the page.
There are a few options regarding how to store the data:
The server can save the identifier and the data for the user in the memory. This is something we can do only during development and we can not rely on sessions saving data in the memory. Saving session data in the memory can cause memory leakage as more users enter our app. In addition if there is a server crash we can loose all the sessions. We can save in memory only during development.
Instead of saving in the cookie only the identifier, we can save in the cookie the entire data. Which means te entire data of the session will be available for our client to see. The browsers also limit the cookie to 4k so the amount of data is limited. Saving in the cookie will also mean larger requests and thus slower requests.
We can use memory data structure store, like redis. This is the prefered solution regarding production usage. We will have to set up a high availability redis server which might take a bit of time and maintenance but doing it properly will mean a fast and reliable solution.
We can also save the session data in the database. This solution will be slower then the redis one and will create overhead on our database since almost every request will query our session table.
To practice session usage we will create the following app. Our application will have a login page. The login page will have a text input for the user to enter the name of the user. We will save the name in the session and direct the user to a welcome page. In the welcome page we will grab the name from the session and print it. We will also try different solutions for session storage. We will use express for our application so lets start by creating our express application.
Lets start by bootstraping a new express application. Create a directory called express-sessions-tutorial, init npm in that directory, and install express.
> mkdir express-session-tutorial
> cd express-session-tutorial
> npm init --yes
> npm install express --save
In the root directory of our project, create a file called app.js which will start an express application and start our we server.
const express = require('express');
const path = require('path');
const app = express();
// so we can use pug as our views templating engine
app.set('views', path.resolve(__dirname, 'views'));
app.set('view engine', 'pug')
// so we can grab post params from req.body
app.use(express.json());
app.use(express.urlencoded());
app.get('/welcome', function(req, res) {
// need to gran the data from the session
})
app.route('/login')
.get(function(req, res) {
res.render('login');
})
.post(function(req, res) {
// login data will be posted here
});
app.listen(3001, function() {
console.log('now listening on port 3001');
});
So lets go over what we did here.
Lets set the views for our express application. Install pug templating with npm:
> npm install pug --save
Create a directory for our views, in our root project directory create a directory called views. In the views directory, create a file called login.pug to hold the view of our login screen with the following code.
doctype html
html
head
title Login App
body
form(method="post" action="/login")
div
label Your Name
input(type="text" name="name" placeholder="type your name")
div
button(type="submit") Submit
This template simply display a simple form which post to the login url. The form has a single input for text and a submit button. More data about pug templating can be found here running your application and going to the login route you should see the login form displayed.
Lets pause for a second and at this point lets examine our application cookies. Start the app and go to the login route where we can see the login form. Open your chrome developer tools by pressing CMD + ALT + I On the developer tools click the Application tab. On the left panel you should see Cookies so expend that and click to examine the cookies set to the domain: http://localhost:3001. You should see the list of cookies is empty. With that in mind lets proceed to connect our app to session.
express-session is the popular middleware to add session to express. We need to install this package with npm:
> npm install express-session --save
lets install this middleware so before the routes and after the loading of the urlencoded middleware, load the session middleware. app.js
const session = require('express-session');
app.use(session({
secret: 'https://www.nerdeez.com'
}))
the secret is used to sign the session id cookie, so only the server can decrypt the actual session id. By default the session store will be in memory (only good for development). After adding the above line, relaunch your app and open the login again and check the cookies like before. This time you should see a cookie called connect.sid the default cookie name the server sends in the response and the browser will send this cookie with every request. In this cookie name the hashed session id is stored. Lets deal with the route for login post and store the name of the user in the session. In app.js in the login post section change to the following:
app.route('/login')
.get(function(req, res) {
res.render('login');
})
.post(function(req, res) {
req.session.name = req.body.name
res.redirect('/welcome');
});
so to store data in the session we simply add attributes to the res.session. Change the welcome route in the app.js to this:
app.get('/welcome', function(req, res) {
// need to gran the data from the session
if (req.session.name) {
res.send('Welcome ' + req.session.name)
} else {
res.send('You are not logged in');
}
});
So we are checking here is we have in the session the key name and if so we are printing it, otherwise we state that the user is not logged in. You can try and run your app again and enter a name in the login and you should be redirected to the welcome screen with the name appearing. So we saw how using session with express is super easy to do.
When user is authenticated in our app using sessions, and the sessions is known from the cookies, and of course the cookies are sent from our browsers automatically, there is a security exploit here that we should be aware of. CSRF stands for Cross Site Request Forgery and it basically means that I am an attacker and I'm missleading you my innocent victim, to send a request to a site you are already authenticated. So if we logged in to our application and we saved user data on the session, and now i am openning a phishing mail to evil.com that sends a request to the site im autenticated, the my browser will automatically send the cookies and the request will be approved. So to protect again this kind of attack, we can generate a unique token, send the token not via cookies and expect to get that token back when the user sends requests to the site and if the user didn't send the token or sent an invalid token we will reject the request. The token will be validated again the user session. There is a middleware that helps us achieve our goal called csurl lets attach this middleware to our application so first install it with npm:
> npm install csurf --save
now if we include the middleware in our app, it will enforce us to send the csrf token on every form or pop an error. Add the following in the app.js after the loading of the session middleware
const csrf = require('csurf');
app.use(csrf())
Now launch the app again and try to submit the login form and you should see a csrf error. To make the login work again we have to send the csrf token with the login request. In the app.js modify the login get route:
app.route('/login')
.get(function(req, res) {
res.render('login', {csrfToken: req.csrfToken()});
})
.post(function(req, res) {
req.session.name = req.body.name
res.redirect('/welcome');
});
Now we are creating a token sending it to the template and also adding the same token to the session. In the form we have to add a hidden field with that token so modify the login.pug
doctype html
html
head
title Login App
body
form(method="post" action="/login")
div
label Your Name
input(type="text" name="name" placeholder="type your name")
div
button(type="submit") Submit
input(type="hidden" name="_csrf" value=csrfToken)
Now launch your app and try to submit again and the app should work again. Now phishing and posting to your app from another domain will fail.
As of now we are storing the session data in memory. As mentioned this solution is only valid in development. Another option is to store session data with cookies, this is easily done with another middleware called cookie-session It's installed easily and there is a good explanation in the link so we will not cover it here. Lets try store the session data with redis. First lets install redis locally. redis can be easily installed using homebrew
> brew install redis
After installation is complete you can run redis locally with the following command:
> /usr/local/Cellar/redis/4.0.11/bin/redis-server
this will run redis on port 6379. Now it's time to connect our session with redis. This is easily done with the package connect-redis
> npm install connect-redis --save
Now to connect it, replace the line in app.js where we added the session middleware, with the following:
const RedisStore = require('connect-redis')(session);
app.use(session({
secret: 'https://www.nerdeez.com',
store: new RedisStore({
host: 'localhost',
port: 6379
}),
resave: false
}));
Connecting redis as a store to our session is really easy. first we require if and call the function with our session middleware. We then create a new instace of it with the connection params to our redis and pass it to the store option of the session middleware. In real production app our redis server will be remote and with high availability. We also need to set the resave option to false which will cause not to resave if there are no changes. In production grade apps this will definetly be the best choice for storing session. But sometimes creating a server for redis and making it high availability is money consuming and time consuming and we would like a good simple solution. We already have a database in our app so its really common to just use our database as session storage. Lets try and connect our session to the database.
We will use postgres for this example. Postgres can be easily installed using homebrew so in your terminal type:
> brew install postgresql
before we launch the postgres server we have to first create a database. In our project root directory, create a folder called db that will contain our database. In the terminal type:
> initdb <absolute-path-to-project>/express-session-tutorial/db -E utf8
> pg_ctl -D <absolute-path-to-project>/express-session-tutorial/db -l logfile start
> createdb sessions-demo
> psql sessions-demo < node_modules/connect-pg-simple/table.sql
The first command will create the database cluster and the second will start a postgres server which will run by default on port 5432, the third command will create our database named sessions-demo, the last command will create the table sessions to hold the session data. Now lets connect the session store. The store for session that will save in a postgres database is called: connect-pg-simple lets install it:
> npm install connect-pg-simple --save
Now replace the part in app.js where we connected the session middleware with the following code:
app.use(session({
store: new (require('connect-pg-simple')(session))({
conString: 'postgres://localhost:5432/sessions-demo'
}),
secret: 'https://www.nerdeez.com',
resave: false
}));
Now you should relaunch the app and everything should work, also if you check your database you should see the session data in the sessions table in our database.
Session is another tool that helps us make the basically stateless web to be statefull. We can store data in sessions and it will be available when the user sends requests to our server. Behind the scene what is happening is that we store a session id in the cookie. the browser sends the cookie and from the session id we can grab the data. We can also easily store the session data in different stores where redis or other memory databases is the prefered and fast way for storing that data.