Sessions

CSCI-UA.0480-008

About HTTP and State

HTTP is a stateless protocol.

That means HTTP requests don't know anything about each other. However, there are situations where we want to maintain state across HTTP requests. What are some of those situations?

  1. authentication (is this client logged in? … maintaining logged in state means the user doesn't have to log in per request!)
  2. any time that we want to store persistent data about a client, like:
    • have they visited the site before
    • what are some user's preferences (personalization)
    • tracking / analyzing their behavior (!?)
    • etc.

Maintaining State Between Requests

OK, fine - HTTP is stateless, but maintaining state can be useful.

So… how might we maintain state or share data between requests?

  • store data on the server about a user
  • link that data to the requests from a particular client
    • by using a session id that represents that client
    • …that session id is always retransmitted back to the server with every request from the same client!
    • which essentially maintains state

About That Session ID

Welp! That sounds terribly insecure. Why should that make you wince just a little bit.

  • once you own that id, you own that session!
  • which means that session ids shouldn't be easy to:
    • steal
    • guess


This means that:

  1. session ids shouldn't be generated sequentially
  2. they shouldn't be present in the query string of a url (where someone shoulder surfing could see it, it appears in request logs, etc.)
  3. they should be adequately long / complex to prevent brute force guessing

Back to Maintaining State

A browser has to keep a session id, and send it over to the server on every request in order to maintain state. What are some potential mechanisms for doing this?

  • add a query string parameter for the session id on each request (is this a good idea? → no! shoulder surfing, logs, etc.)
  • add a secret form input for every page (input type is hidden), but…same problem as above if using get, otherwise every request isn't a post
  • cookies! - text files stored by your browser (Chrome actually stores cookies in a sqlite database, which is essentially just a text file)
  • they can store a session id which links to more data on the server
  • as well as client side data (though there are better ways to do this)


Check out the Open Web Application Security Project for more details

Um What? How Does That Work?

Here's how state is maintained between requests using cookies.

  1. the server generates a session id for an http request
  2. as part of the response, it tells the browser to store a session id in a cookie (to set a cookie)
  3. upon receiving the response, your browser creates or updates a cookie (tied to the domain that was visited)
  4. it contains some identifier (the session id)
  5. when your browser makes a request to the server, it sends along that identifier
  6. the server finds the session associated with that identifier
  7. the session store can be as simple as in-memory or file-based store… or it can be a database!
  8. you can store data for that user's session, including authentication, in the session store

First… check out the documentation on cookies:

  1. Cookies on mdn
  2. Cookies on nczonline
  3. Cookies and Security on nczonline

Cookies are stored by your browser, but how do they get there?

  1. the server sets a header in an http response called Set-Cookie (multiple Set-Cookie's in a single http response are allowed)
  2. this header instructs the browser to create a cookie
  3. Set-Cookie header values can specify (separated by ;'s):
    • an arbitrary name=value pair
    • expiration / how long a cookie is valid for
    • various security options (we'll see later)
  4. now, every request that the browser makes to the domain that set the cookie will contain a Cookie header
    • the value is all of the cookies that the browser has for that domain, separated by semicolons
    • (the original security options and expiration are not sent back)

An http response that sets a few cookie values.


HTTP/1.0 200 OK
Content-type: text/html
Set-Cookie: foo=bar
Set-Cookie: baz=qux 
Set-Cookie: session_id=5d6d473c-a370-44c8-bff0-4f28efd7c92a; HttpOnly; Secure

An http request that sends back cookies.


GET / HTTP/1.1
Cookie: foo=bar;baz=qux

Note that:

  • you can use multiple Set-Cookie headers in one response
  • the client sends all values in a single Cookie header in the request


We'll discuss HttpOnly and Secure in a moment

Session vs Permanent Cookies

Cookies will be deleted when the client is shut down (for example, when you quit your browser). These are session cookies.

For cookies that last longer (or shorter!), you can create permanent cookies that expire at a certain date or after a certain length of time.

You can do this by adding options to the Set-Cookie header

  • Expires (date)
    • Set-Cookie: foo=bar; Expires=Thu, 29 Oct 2016 07:28:00 GMT;
  • Max-Age (number of seconds)
    • Set-Cookie: foo=bar; Max-Age=300;


Note that a server cannot force the browser to delete cookies! … but it can set an expiration date which will hopefully convince the client to eventually delete them.

Security Options

In addition to name value pairs of arbitrary data, there are also options that can be set through the Set-Cookie header (also separated by semicolons):

  • Domain - cookies sent will only be valid for this domain (default is current domain)
  • Path - cookies sent will only be valid for this path (default is all paths)
  • HttpOnly - only allow reading of cookies via http, don't allow JavaScript!!!!why do this?3rd party JavaScript included on your page is allowed to read cookies for that domain!?
  • Secure - cookies will only be sent if the request is encrypted (using SSL/HTTPS)


Out of these, definitely use HttpOnly and Secure… though for most of class, we'll be omitting Secure until we get to SSL/HTTPS.

Sessions

Ok, so back to this idea of sessions. Sessions allow you to:

  • store data on a per-session basis
  • by maintaining a small piece of data on the client (via cookies)
  • that matches with data on the server
  • that means… different clients will have different sessions (and consequently different state)
  • a session is ended, from the browser perspective, when the browser is closed

Creating Your Own Session Management

You can create your own session management by creating custom middleware that:

  1. has an in-memory store of session ids (read: global variable)
  2. checks every request for a Cookie header
  3. if there is no Cookie header that contains a session id, it'll generate a session id (using the crypto module)
    • then sends back the Set-Cookie header with that id
  4. however, if there is a Cookie header with a session id, it'll:
    • search for that id in the session store
    • retrieve that data
    • add it as a property on the request object so that it can be accessed programmatically
    • send back a response like usual

Creating Your Own Session Management Continued

You can try writing some session management middleware by using

  • req.get - to retrieve the Cookie header
  • parsing that header into name/value pairs
  • the crypto module to create a session id
  • res.set or res.append- to set the Set-Cookie header


Note that… res.set doesn't allow you to set multiple Set-Cookie headers (it only leaves the last cookie set). So res.append allows you to add values to an already set response field.


Of course, as you might expect, session middleware already exists… but we'll try a little bit of the above to see what's going on.

Managing Cookies on the Client Side

Stock Chrome always you to view cookies as well as delete them: →

  1. Web Developer Tools
  2. Application (tab bar on top of web developer tools)
  3. Cookies (left panel, under "Storage")
  4. use dropdown to see cookies
  5. or chrome://settings/cookies

Setting Some Cookies

Use res.set or res.append to set the Set-Cookie header


app.get('/make-a-cookie', function(req, res) {
    // used append so that we can Set-Cookie more than once
    res.append('Set-Cookie', 'MY_SESSION_ID=123; HttpOnly');
    res.append('Set-Cookie', 'color=#00ff00');
    res.send('made you a cookie');
});


Watch all following requests send those cookies back via the Cookie header.!

HttpOnly?

Here's an example of displaying cookies using client side JavaScript, using document.cookie.


app.get('/peek', function(req, res) {
    // uncomment this: 
    // const s = "alert(document.cookie);";
    res.send('' + 'check out yr cookies!');
});


If you used the example in the previous slide, the HttpOnly cookie, MY_SESSION_ID, should not appear in the alert.

Session Middleware

The previous slides sketch out a way of implementing manual cookie management.

For "production", instead of using a custom solution , you can use the express-session module!

  • handles session id generation for you!
  • deals with setting cookies on the browser, parsing cookies and retrieving session ids from requests
  • most importantly, it provides a property on your request object that contains session data: req.session!
    • on the server, you can store data in a user's session by using req.session.someData = 'some value'
    • … and, of course, you can read it back out by using req.session.someData


How might you use this?

  • tracking views to a page: in a route handler for a path, req.session.count += 1
  • keeping preferences: in route that handles form input, req.session.favoriteColor = req.body.favoriteColor

Setting Up Session Middleware

Use npm to install as usual: npm install express-session

Boilerplate setup.


const express = require('express');

Include the express-session module…


const session = require('express-session');

const app = express();

app.set('view engine', 'hbs');
app.use(express.urlencoded({ extended: false }));

Storing Data in Your Session

Set up some session options (the secret should really be externalized and not in version control, but we'll keep it here for convenience).


const sessionOptions = { 
	secret: 'secret for signing session id', 
	saveUninitialized: false, 
	resave: false 
};
app.use(session(sessionOptions));

What's the Deal With These Options

Check out the docs for details on all of the options. The ones that we set explicitly are:

  • secret - used to sign the session id cookie to prevent tampering (and possibly to ensure length/complexity to make unguessable)
  • saveUnitialized: false - don't save new empty session (to preserve space)
  • resave: false - prevents session data from being resaved if session data is unmodified


Some others interesting ones that we don't explicitly set:

  • store - where session data is stored, defaults to in memory storage
  • genid - function that generates session id

Default MemoryStore

By default, express-session uses an in memory session store, MemoryStore

  • this works great for development and prototyping because you don't have to set up a database to store sessions
  • however, from the module's documentation:
  • "Warning The default server-side session storage, MemoryStore, is purposely not designed for a production environment. It will leak memory under most conditions, does not scale past a single process, and is meant for debugging and developing."

So, for our purposes, it's ok to use the in-memory store, but any production application should use another compatible session store, such as memcache, mongodb, etc.

req.session, req.session.id

Once you have all of the setup finished, you'll have a new property available on your request object. It will allow you to use:

  1. req.session - an object that you can read and write session data to
  2. req.session.id and req.sessionID - the id for the session (both are the same and both are read only)

Saving Data in a Session

Let's create a simple-form that:

  • allows a user to submit their name using a form
  • the form page will have a heading that consists of the user's submitted name (so, before submitting data, the name will be blank, but afterwards, it will display the submitted data)
  • the form is at /
  • the form will post to itself (the same url that the form is on)
  • the name submitted will be stored in the session
  • it will redirect back to the form

Routes

Our usual routes, but note the use of req.session.



app.get('/', function(req, res) {
    const name = req.session.myName || '';
    res.render('index', {'myName':name});
});

app.post('/', function(req, res) {
    console.log(req.body);
    req.session.myName = req.body.firstName;
    res.redirect('/');
});


app.listen(3000);

And, In the Template


myName: {{myName}}

my name: section

Try Entering Data With Two Different Browsers Or Incognito Mode

What do you think will happen?

Session data will be unique to each browser session (so you can have foo for one name and bar for another name if you're using two different browsers)

Let's Prove That There's Some Data Stored on the Client Side

  1. chrome://settings/cookies
  2. find localhost:3000
  3. check out the content of connect.sid

What do you think will happen if we request the page with curl? Will the name be there?


curl localhost:3000 

Nooope… no info to identify the session, so name isn't there.

We can actually use curl to send cookies by using the --cookie flag. Let's copy over the cookie data…


curl localhost:3000 -v --cookie "connect.sid=..."

Shutting Down the Server

What will happen if we restart the server? Will the session data still be present?

We're using an in-memory session store, so, the session data will not be persisted.