HTTP is a stateless protocol. That means that when you load a page in your browser, and then you navigate to another page on the same website, neither the server nor the browser has any intrinsic way of knowing that it’s the same browser visiting the same site. Another way of saying this is that the way the Web works is that every HTTP request contains all the information necessary for the server to satisfy the request.
This is a problem, though: if the story ended there, we could never “log in” to anything. Streaming media wouldn’t work. Websites wouldn’t be able to remember your preferences from one page to the next. So there needs be a way to build state on top of HTTP, and that’s where cookies and sessions enter the picture.
Cookies, unfortunately, have gotten a bad name thanks to the nefarious things that people have done with them. This is unfortunate because cookies are really quite essential to the functioning of the “modern web” (although HTML5 has introduced some new features, like local storage, that could be used for the same purpose).
The idea of a cookie is simple: the server sends a bit of information, and the browser stores it for some configurable period of time. It’s really up to the server what the particular bit of information is: often it’s just a unique ID number that identifies a specific browser so that the illusion of state can be maintained.
There are some important things you need to know about cookies:
Cookies are not magic: when the server wishes the client to store a cookie, it sends a header called Set-Cookie containing name/value pairs, and when a client sends a request to a server for which it has cookies, it sends multiple Cookie request headers containing the value of the cookies.
To make cookies secure, a cookie secret is necessary. The cookie secret is a string that’s known to the server and used to encrypt secure cookies before they’re sent to the client. It’s not a password that has to be remembered, so it can just be a random string. I usually use a random password generator inspired by xkcd to generate the cookie secret.
It’s a common practice to externalize third-party credentials, such as the cookie secret, database passwords, and API tokens (Twitter, Facebook, etc.). Not only does this ease maintenance (by making it easy to locate and update credentials), it also allows you to omit the credentials file from your version control system. This is especially critical for open source repositories hosted on GitHub or other public source control repositories.
To that end, we’re going to externalize our credentials in a JavaScript file (it’s also fine to use JSON or XML, though I find JavaScript to be the easiest appraoch). Create a file called credentials.js:
module.exports={cookieSecret:'your cookie secret goes here',};
Now, to make sure we don’t accidentally add this file to our repository, add credentials.js to your .gitignore file. To import your credentials into your application, all you need to do is:
varcredentials=require('./credentials.js');
We’ll be using this same file to store other credentials later on, but for now, all we need is our cookie secret.
If you’re following along by using the companion repository, you’ll have to create your own credentials.js file, as it is not included in the repository.
Before you start setting and accessing cookies in your app, you need to include the cookie-parser middleware. First, npm install --save cookie-parser, then:
app.use(require('cookie-parser')(credentials.cookieSecret));
Once you’ve done this, you can set a cookie or a signed cookie anywhere you have access to a request object:
res.cookie('monster','nom nom');res.cookie('signed_monster','nom nom',{signed:true});
Signed cookies take precedence over unsigned cookies. If you name your signed cookie signed_monster, you cannot have an unsigned cookie with the same name (it will come back as undefined).
To retrieve the value of a cookie (if any) sent from the client, just access the cookie or signedCookie properties of the request object:
varmonster=req.cookies.monster;varsignedMonster=req.signedCookies.monster;
You can use any string you want for a cookie name. For example, we could have used 'signed monster' instead of 'signed_monster', but then we would have to use the bracket notation to retrieve the cookie: req.signedCookies['signed monster']. For this reason, I recommend using cookie names without special characters.
To delete a cookie, use req.clearCookie:
res.clearCookie('monster');
When you set a cookie, you can specify the following options:
domain
path
maxAge
expires option, but the syntax is frustrating. I recommend using maxAge.)
secure
httpOnly
signed
res.signedCookies instead of res.cookies. Signed cookies that have been tampered with will be rejected by the server, and the cookie value will be reset to its original value.
As part of your testing, you’ll probably want a way to examine the cookies on your system. Most browsers have a way to view individual cookies and the values they store. In Chrome, open the developer tools, and select the Resources tab. In the tree on the left, you’ll see Cookies. Expand that, and you’ll see the site you’re currently visiting listed. Click that, and you will see all the cookies associated with this site. You can also right-click the domain to clear all cookies, or right-click an individual cookie to remove it specifically.
Sessions are really just a more convenient way to maintain state. To implement sessions, something has to be stored on the client; otherwise, the server wouldn’t be able to identify the client from one request to the next. The usual method of doing this is a cookie that contains a unique identifier. The server then uses that identifier to retrieve the appropriate session information. Cookies aren’t the only way to accomplish this: during the height of the “cookie scare” (when cookie abuse was rampant), many users were simply turning off cookies, and other ways to maintain state were devised, such as decorating URLs with session information. These techniques were messy, difficult, and inefficient, and best left in the past. HTML5 provides another option for sessions, called local storage, but there’s currently no compelling reason to use this technique over tried and true cookies.
Broadly speaking, there are two ways to implement sessions: store everything in the cookie, or store only a unique identifier in the cookie and everything else on the server. The former are called “cookie-based sessions,” and merely represent a convenience over using cookies. However, it still means that everything you add to the session will be stored on the client’s browser, which is an approach I don’t recommend. I would recommend this approach only if you know that you will be storing just a small amount of information, that you don’t mind the user having access to the information, and that it won’t be growing out of control over time. If you want to take this approach, see the cookie-session middleware.
If you would rather store session information on the server, which I recommend, you have to have somewhere to store it. The entry-level option is memory sessions. They are very easy to set up, but they have a huge downside: when you restart the server (which you will be doing a lot of over the course of this book!), your session information disappears. Even worse, if you scale out by having multiple servers (see Chapter 12), a different server could service a request every time: session data would sometimes be there, and sometimes not. This is clearly an unacceptable user experience. However, for our development and testing needs, it will suffice. We’ll see how to permanently store session information in Chapter 13.
First, install express-session (npm install --save express-session); then, after linking in the cookie parser, link in express-session:
app.use(require('cookie-parser')(credentials.cookieSecret));app.use(require('express-session')());
The express-session middleware accepts a configuration object with the following options:
key
connect.sid.
store
MemoryStore, which is fine for our current purposes. We’ll see how to use a database store in Chapter 13.
cookie
path, domain, secure, etc.). Regular cookie defaults apply.
Once you’ve set up sessions, using them couldn’t be simpler: just use properties of the request object’s session variable:
req.session.userName='Anonymous';varcolorScheme=req.session.colorScheme||'dark';
Note that with sessions, we don’t have to use the request object for retrieving the value and the response object for setting the value: it’s all performed on the request object. (The response object does not have a session property.) To delete a session, you can use JavaScript’s delete operator:
req.session.userName=null;// this sets 'userName' to null,// but doesn't remove itdeletereq.session.colorScheme;// this removes 'colorScheme'
“Flash” messages (not to be confused with Adobe Flash) are simply a way to provide feedback to users in a way that’s not disruptive to their navigation. The easiest way to implement flash messages is to use sessions (you can also use the querystring, but in addition to those having uglier URLs, the flash messages will be included in a bookmark, which is probably not what you want). Let’s set up our HTML first. We’ll be using Bootstrap’s alert messages to display our flash messages, so make sure you have Bootstrap linked in. In your template file, somewhere prominent (usually directly below your site’s header), add the following:
{{#if flash}}
<div class="alert alert-dismissible alert-{{flash.type}}">
<button type="button" class="close"
data-dismiss="alert" aria-hidden="true">×<button>
<strong>{{flash.intro}}</strong> {{{flash.message}}}
</div>
{{/if}}Note that we use three curly brackets for flash.message: this will allow us to provide some simple HTML in our messages (we might want to emphasize words or include hyperlinks). Now let’s add some middleware to add the flash object to the context if there’s one in the session. Once we’ve displayed a flash message once, we want to remove it from the session so it isn’t displayed on the next request. Add this code before your routes:
app.use(function(req,res,next){// if there's a flash message, transfer// it to the context, then clear itres.locals.flash=req.session.flash;deletereq.session.flash;next();});
Now let’s see how to actually use the flash message. Imagine we’re signing up users for a newsletter, and we want to redirect them to the newsletter archive after they sign up. This is what our form handler might look like:
app.post('/newsletter',function(req,res){varname=req.body.name||'',=req.body.||'';// input validationif(!.match(VALID_EMAIL_REGEX)){if(req.xhr)returnres.json({error:'Invalid name email address.'});req.session.flash={type:'danger',intro:'Validation error!',message:'The email address you entered was not valid.',};returnres.redirect(303,'/newsletter/archive');}newNewsletterSignup({name:name,:}).save(function(err){if(err){if(req.xhr)returnres.json({error:'Database error.'});req.session.flash={type:'danger',intro:'Database error!',message:'There was a database error; please try again later.',}returnres.redirect(303,'/newsletter/archive');}if(req.xhr)returnres.json({success:true});req.session.flash={type:'success',intro:'Thank you!',message:'You have now been signed up for the newsletter.',};returnres.redirect(303,'/newsletter/archive');});});
Note how the same handler can be used for AJAX submissions (because we check req.xhr), and that we’re careful to distinguish between input validation and database errors. Remember that even if we do input validation on the frontend (and you should), you should also perform it on the backend, because malicious users can circumvent frontend validation.
Flash messages are a great mechanism to have available in your website, even if other methods are more appropriate in certain areas (for example, flash messages aren’t always appropriate for multiform “wizards” or shopping cart checkout flows). Flash messages are also great during development, because they are an easy way to provide feedback, even if you replace them with a different technique later. Adding support for flash messages is one of the first things I do when setting up a website, and we’ll be using this technique throughout the rest of the book.
Sessions are useful whenever you want to save a user preference that applies across pages. Most commonly, sessions are used to provide user authentication information: you log in, and a session is created. After that, you don’t have to log in again every time you re-load the page. Sessions can be useful even without user accounts, though. It’s quite common for sites to remember how you like things sorted, or what date format you prefer—all without your having to log in.
While I encourage you to prefer sessions over cookies, it’s important to understand how cookies work (especially because they enable sessions to work). It will help you with diagnosing issues and understanding the security and privacy considerations of your application.