Getting, Setting, and Understanding Web Cookies using Express and WebdriverIO

Cookies, both the tasty kind and the web browsing kind, aren’t new to me. I’ve clicked my fair share of “this site uses cookies” pop-ups, but until recently, I hadn’t encountered cookies in my work as a programmer.

Being newly tasked with the configuration and testing of cookies, I realized that, although cookies are ubiquitous on the web, I had glossed over several important aspects. While trying to fill in the gaps in my understanding, I had a hard time finding a source that answered all of my cookie-related questions. I hope that this blog post will serve as that resource for others.

What Are Cookies, and Why Would I Use Them?

Cookies are small pieces of text data stored by a user’s browser (on the user’s computer). When the user visits a given website*, the cookie is sent with HTTP requests to provide information about the user.

This information might describe whether the user is already logged in, the last username used to log in (to help speed up the login process), personal information/settings used to customize the webpage for that user, and so on.

*Depending on the rules of the cookie’s domain and whether it accepts other domains

Fun fact: the term “cookies” was coined by Lou Montulli, an engineer working at Netscape. Cookies were created so users could have persistent items in their shopping cart (although cookies aren’t usually used for this purpose now). Lou Montulli is often credited for creating the <blink> tag as well, although he considers “the blink tag to be the worst thing I’ve ever done for the Internet.”  Disagree, Lou!

Where Do These Cookies Come From?

If it’s your first time visiting a website, you (or rather, your browser) won’t have a cookie to send with requests. If the website makes use of cookies, its code will need to generate a cookie for you and send it back in its response to your HTTP request.

Then, the browser stores that cookie and sends it during subsequent visits to pages on that website (or related websites, which we’ll get to later).

Note: A website can send multiple cookies. The limits are typically something like 300 cookies in total, 4KB per cookie, 20 cookies per domain… which is a lot of cookies.

Cookie vs. Header?

That sounds quite a bit like an HTTP header… what’s the difference?

Cookies are passed from client to server (and server to client) as HTTP headers.

The “Cookie” header is used to send previously-obtained cookies from the client to server during an HTTP request.

Example:  Cookie:name=value

And the “Set-Cookie” HTTP response header is used by the server in its response field to send newly created cookies back to the client for future use.

Example:  Set-Cookie:name=value

What’s in a Cookie?

At a minimum, each cookie must contain a name and a value. It can also contain some additional attributes:

Domain

The domain path specifies the domain/subdomain(s) where the browser should send this cookie in the future. Thus, it defines the scope of the cookie.

A cookie’s domain has to match the resource’s top domain and subdomains.  So why bother setting it?  If you don’t set the domain attribute, the effective domain is the domain of the request.  If you do set it, then subdomains will be included as well.

Path

Likewise, the path attribute also scopes down where the browser should send a cookie.  For example, a path value of “/books” would tell the browser to only send the cookie to requests within that subdirectory, whereas a value of “/” would send the cookie to the entire domain.

Expires

This attribute provides a date and time when the browser should delete the cookie.  An example of the format for the expires attribute is Sun, 19 Aug 2012 00:00:01 GMT.

Max-Age

Alternatively, if you hate date formats, you can always just set a time in milliseconds. After that point (now + X milliseconds), the cookie will expire, and the browser will delete it.

Secure

This indicates whether cookies must be transmitted over encrypted connections (like HTTPS). If the secure attribute is set, the browser knows it should only return the cookie over an encrypted connection.

HttpOnly

These cookies have the “HttpOnly” attribute, meaning that client-side scripts can’t access them.  This helps to mitigate XSS attacks where someone could steal your cookies.

If that isn’t enough cookies for you, check out RFC2965 and RFC6265.

What Kinds of Cookies Are There?

There are different types of cookies, and those types can be discerned by the attributes discussed in the previous section.

Session cookies (a.k.a. in-memory cookies, transient cookies, or non-persistent cookies) exist only while the user is on the website. They’re temporary, and the browser deletes the cookie after the user closes the browser. A cookie is a session cookie if there’s no expiration date set; in other words, if the expires and max-age attributes aren’t set.

In contrast, there are persistent cookies, which have set expiration dates and/or lifespans. Until their expiration date arrives, the browser will send the cookie along with all requests every time the user visits the website or related resource (i.e. ads). So, unlike session cookies, persistent cookies can be used across multiple sessions.

Additionally, there are first-party and third-party cookies.  First-party cookies originate from the website you’re currently viewing, whereas third-party cookies originate from another website, or will be sent to another website.  Third-party usage can be disabled using the SameSite attribute, which can help mitigate CSRF attacks.

An ad tracking service may set cookies on various websites that display its ads, allowing the ad service to create a browsing history for that user across hundreds of websites.  This can be blocked with privacy tools such as Privacy Badger and Ghostery.

If you’d like to see your cookies in Chrome, you can go to chrome://settings/siteData.  You can also find your cookies in browsers through menus, such as Preferences > Privacy & Security > Cookies and Site Data in Firefox.

GDPR Say What?

Close… it’s actually a different EU law that requires browsers to disclose the use of cookies. The Privacy and Electronic Communications Directive, signed into law in 2002 and updated in 2009, requires opt-in consent rather than opt-out.

The law was clarified further in 2012. Now the consent requirement does not apply to cookies that are “strictly necessary for the delivery of a service requested by the user.”

How Do I Use Cookies with ExpressJS?

My current project uses Express. I looked at their cookie docs, which recommend using the cookie-parser library.

Here’s a quick, contrived example endpoint that uses cookie-parser to check for a cookie of a given name. It then sets and returns another cookie based on that.


var cookieParser = require("cookie-parser");
var express = require('express');

var app = express();
app.use(cookieParser());

app.get('/cookie-test', function(req, res){
    // check the request for existing cookies named "example"
    let existingCookie = req.cookies["request-example"];

    if (existingCookie) {
        // to send a cookie in the response
        res.cookie('already-had-cookie', 'true').send();
    } else {
        res.cookie('already-had-cookie', 'false').send();
    }
});

We can also set other attributes–for example, this cookie that will expire in one week (604800000 milliseconds).


res.cookie("persistent-example", "value", { maxAge: 604800000 });

You can also clear cookies if need be:


res.clearCookie("cookie-name");

How Do I Test Cookies?

We’re currently using WebdriverIO for acceptance testing, so the following examples use WebdriverIO’s API for getting, setting and deleting cookies.


// if need be, we can clear cookies from previous tests
browser.deleteCookie();

// set cookie with attributes as described earlier
browser.setCookie({
    httpOnly: false,
    name: "cookie-name-here",
    path: "/",
    secure: false,
    sessionId: browser.sessionId,
    value: "cookie-value-here",
    state: "success" // or "failure"
});

// retrieving cookie to assert value
let retrievedCookieValue = browser.getCookie("cookie-name-here").value;

After you’ve added your assertions, you should be able to automate testing of cookies that you’ve added to your Express app.

I hope that this blog post has made working with web cookies a little easier!