Browser Cookies 101
A Detailed Introduction: incl. Implementation, Security, Alternatives, and Recent Developments
When building web applications, it is likely that you’ll eventually come across cookies — and no, not the chocolate chip variety. A browser-implemented technology, cookies can be leveraged to implement modern internet functionality.
At its most basic level, an HTTP cookie (also known as a web, or browser cookie) is a small piece of data that typically originates from an HTTP server. Cookies cannot exceed 4KB of textual key=value
data, and are stored locally by a client’s browser on their disk. Cookies are private to the domain that sets them; in plain English — the browser only allows websites to read, write, and delete their own cookies. Most browsers limit the number of cookies any single domain can set to approx. 20–25 cookies (the total number depends on the browser itself) in order to prevent local disk space consumption. Also, because cookies are browser-specific, a server cannot utilize cookies set in one browser for the same client in another browser. That said, different tabs or windows from the same browser can all access that client’s cookies.
Cookies were first introduced to the web in 1994, and have been further defined as recently as 2011, in the RFC 6265 standards. When you consider that HTTP is a stateless protocol, the creation of cookies allowed web developers to implement state in an otherwise stateless HTTP request. The most common uses for cookies include managing a client’s session, or authentication of users; tracking clients; and personalization. By using cookies, a domain can recognize the same client and keep them logged into an application, serve them relevant, personalized information (think social media newsfeeds), or even keep track of their navigation (third-party advertisement).
On a functional level, a client makes an initial HTTP request, which is ultimately received by the server. The server responds with an HTTP response with a directive to set a cookie. The browser receives both the response and the cookie, and persists the cookie locally on the client’s disk. When future HTTP requests are sent from the same client, the cookie is included, and the server can utilize the associated data in the cookie to recognize the same client and create stateful responses. As a note, the server is unable to confirm if the cookie was set on a secure origin, or even tell where the cookie was originally set, it can only read the cookies associated with the respective domain.
As an aside, cookies can be set and read both on the server-side, as described above, and also on the client-side, as exposed in the browser’s Document object. Below, we’ll dive more deeply into implementing both server-side and client-side cookies.
Implementing Cookies
When implementing cookies, it’s important to know that there are two types of cookies: session and permanent cookies. A cookie, by default, will persist as long as the browser window is open. Upon closure, a cookie is typically deleted along with the client’s current session; hence, these are session cookies. A permanent cookie, therefore, would last longer than the current session. This can be accomplished by setting an expiry date within the cookie itself. With browsers now implementing session restoring, a session cookie will not be deleted immediately upon a browser window being closed and will act like a permanent cookie.
Creating Cookies Server-Side
When creating a cookie, a server can send a Set-Cookie
header along with the HTTP response back to the client. This header will alert the browser to create and store the cookie. When further HTTP requests are made, the browser will automatically include the Cookie
header.
The most basic cookie is set with the following syntax:
Set-Cookie: key=value;
Implementing a Cookie
header with different servers will look slightly different due to the nuances of syntax across different languages.
With NodeJS, a cookie could be set as simply as response.setHeader('Set-Cookie', 'key=value');
with a string value, or with an array of strings to include multiple values associated with the same cookie: response.setHeader('Set-Cookie', ['id=1', 'name=Jane']);
To delete a cookie, you can set the Expires
flags (more details below) to a date in the past, the Max-Age
flag to 0
, or with ExpressJS by utilizing the clearCookie()
method with all of the same cookie flags.
In Ruby on Rails, a cookie can be read and set directly through the ActionController Cookies
method. Reading a cookie only returns the value that it holds, not the actual cookie object. A basic cookie could be set with cookies[:name] = “Jane”
or will multiple values as
cookies[:name] = {
value: 'Jane',
expires: 1.week,
domain: 'medium.com'
}
To read in Rails, you simply need to call cookies[:name]
and it will return the value. To delete a cookie, the delete
method can be utilized: cookies.delete :name
but be aware, if the domain was specified when setting the cookie, it must also be specified when deleting a cookie in Rails.
Cookie Flags
In addition to the data that you want to pass into the cookie, additional flags can be set for further specificity.
Expires
and Max-Age
In order to create a permanent cookie, an expiry date needs to be set. This can be accomplished with either a specific date and the Expires
flag, or a specific length of time with the Max-Age
tag.
Set-Cookie: id=1; Expires=Friday, 3 Apr 2020 00:00:00 GMT;
Set-Cookie: id=1; Max-Age=31536000;
(this cookie would expire in one year)
Domain
and Path
The Domain
and Path
flags determine the scope of a cookie, or what sites a cookie may be sent to. When set, Domain
specifies allowed hosts, including subdomains. Without this flag, the default behavior is to only allow the host of the current site, excluding relevant subdomains. The Path
flag specifies a URL path that must be included in the requested URL in order to allow the cookie to be sent with the HTTP request. When setting the flag, by using the forward slash, subdirectories will also be included, i.e. Path=/test
will also allow Path=/test/1
and Path=/test/1/custom-path
Set-Cookie: id=1; Path=/custom-path;
Set-Cookie: id=1; Domain: medium.com;
Secure
and HttpOnly
In an encrypted request sent over HTTPS protocol to a server, cookies can include the Secure
flag, though it doesn’t offer real protection. Cookies themselves are inherently insecure and sensitive information should never be stored in a cookie. Some browsers (Chrome and Firefox 52) have started preventing insecure site from setting cookies with the Secure
flag.
Further, cookies can also be set with the HttpOnly flag. This prevents the cookies from being accessible in the Document object to Javascript; they will only be sent to the server. For cookies that only need to persist server-side session information, this flag should be utilized.
Set-Cookie: id=1; Secure; HttpOnly;
SameSite
In order to prevent a cookie from being send cross-site requests, understanding that the site here is the registrable domain, the SameSite
flag can be set. It can have one of the following attributes: None
, Lax
, or Strict
(case-insensitive). None
allows cookies to be sent either same-site or cross-site. Lax
will allow cookies to be sent when a client navigates to the domain from an external site, such as via link. Otherwise, the cookies are withheld on cross-site subrequests. Lax
is commonly the browser default. Strict
only allows the browser to send cookies for same-site requests.
Set-Cookie: id=1; SameSite=Strict;
Setting Cookies Client-Side
Cookies can also be created on the client-side by accessing the Document.cookie
object with Javascript. However, as noted above, if the HttpOnly
flag was set when the cookie was created, then these cookies are not accessible from the client-side.
In order to create a cookie, similar to the NodeJS example above, you simply pass in the value and any applicable flags as a string: document.cookie = “name=Jane”;
and you can read directly from the object: console.log(document.cookie);
which would return “name=Jane”
. In order to delete a cookie, simply overwrite the value as empty, and set an expiry data in the past: document.cookie = “name=; expires=Thu, 01 Jan 1970 00:00:00 UTC;”;
Security with Cookies
When implementing cookies, it’s important to be aware of and consider the security risks. There are two main methods to breach security: XSS and CSRF attacks. We’ll explore both.
Cross-Site Scripting (XSS)
One of the most common usages of cookies is to store client data and an authenticated user session. If a hacker were to steal the cookie with this information, that client’s data or current user session could be exploited. XSS, or Cross-Site Scripting, allows hackers to inject malicious client-side code in order to bypass controls and impersonate users. The browser itself cannot determine if the script is untrustworthy, so the hacker can access that client’s cookies, as well as tokens and site-specific information.
In order to mitigate these attacks, setting cookies with the HttpOnly
flag prevents hackers from accessing a client’s cookies through Javascript. Additional security to prevent XSS can also be implemented with Content Security Policies.
Cross-Site Request Forgery (CSRF)
A Cross-Site Request Forgery is an attack that utilizes an authenticated user’s session and sends a site unwanted commands. It’s possible to create a CSRF by including malicious parameters in a URL that should go somewhere else. For example, if a client is logged into their bank account with valid cookies, it is possible that another tab or browser window (really, another site) has loaded an “image” with a URL request to withdraw money from your account and transfer it elsewhere. When the HTML on that other site loads, it will trigger the transfer immediately without preventative measures.
To prevent these attacks, there are several avenues to consider when focused on cookies. The first is to set the SameSite
flag to either Strict
or Lax
for sensitive user actions to prevent untrustworthy cross-site requests. CSRF tokens (generated upon user sign-on) could also be implemented in hidden input fields for forms. The server can compare the received token against the expected token as another form of validation. By deploying both SameSite
cookies and CSRF tokens, browsers can better protect against CSRF attacks — even those originating from a different subdomain.
Tracking & Privacy with Third-Party Cookies
Like session and permanent cookies, cookies can also fall into one of two categories: first-party or third-party cookies. All cookies are set with a specific domain (the developer can also specify this with the Domain
flag). When the client’s current domain matches the domain of a cookie, that cookie is considered a first-party cookie. When the domain is different, it is a third-party cookie.
When cookies are set through a third-party component — either an image hosted on another domain’s server, or a component similarly hosted on another domain’s server (i.e. an ad banner) — they create third-party cookies. These cookies are primarily used for tracking and advertising purposes. Some browsers (more information below) allow clients to block third-party cookies, though the default behavior in browsers for many years was to allow third-party cookies by default.
An additional DNT
(Do Not Track) header can be included in HTTP requests to allow the client to indicate their preference for privacy instead of more personalized content.
Cookie Alternatives
With the advancement of HTML5, Web Storage allowed developers to implement state in a more secure, less structured manner. Every browser supports Web Storage.
The storage limits far exceed cookies (at least 5MB as compared to 80KB collectively), and any page associated with the same origin — both domain and protocol — can access Web Storage. As a short aside, CORS (Cross-Origin Sharing Standard) can further enable cross-site HTTP requests, especially in conjunction with the Fetch
API. Similarly to session and permanent cookies, Web Storage provides two objects to store data: sessionStorage
and localStorage
, respectively. The localStorage
object persists data beyond when the browser is closed, without expiration, whereas sessionStorage
stores the data until the client closes the specific browser tab.
In order to set, read or delete data set in the Web Storage, you can implement as follows:
localStorage.setItem("foo", "bar")
localStorage.getItem("foo")
localStorage.removeItem("foo")// orsessionStorage.setItem("foo", "baz")
sessionStorage.getItem("foo")
sessionStorage.removeItem("foo")
With the popularization of Single Page Applications (SPAs), setting and maintaining cookies in order to implement state across multiple HTTP requests is somewhat moot; a SPA can dynamically render new content without refreshing the page.
Furthermore, the rise of JWTs (JSON Web Token) have mitigated the need for a standardized, secure container format — optionally validated and/or encrypted — to transfer information between two parties. JWTs can be utilized to communicate information between the server and browser, commonly for authentication and authorization of users. To be clear, JWTs can be implemented with both cookies and local
/sessionStorage
; with a SPA, the JWT can easily be set in Web Storage (either local
or session
as most appropriate) after an initial API call to procure the token and then added to every subsequent request to the server.
The potential drawbacks of utilizing Web Storage is that it can also be targeted by XSS attacks, and information stored (such as a JWT) needs to be manually sent with each new HTTP request. Moreover, sessionStorage
doesn’t allow for multiple tab or browser access, and localStorage
is never automatically purged — it must be cleared manually. On the other hand, session cookies are deleted upon a client closing the browser, and permanent cookies can be managed with expiry dates.
Recent Legislation
In 2009, the European Union passed Directive 2009/136/EC, also known as the e-Privacy Directive. Specifically, it required all websites that are owned by EU companies or international sites that cater to EU citizens to inform clients of cookie usage and gain their consent before persisting cookies in the browser. Clients also need to be given the option to disallow cookies. The utilization of cookies with the express purpose of basic functionality, i.e. a checkout cart, were excluded. To be clear, utilization of Web Storage was also considered to be included in the directive. The intent was to better educate consumers regarding what personal information is being collected and how it’s being used. As of May 2012, each member state in the EU had enacted legislation to fulfill the directive.
In May 2018, a more stringent and expansive law, the General Data Protection Regulation (GDPR) established that companies were required to follow ten principles in order to collect and use consenting clients’ data. The principles include: lawfulness, fairness, transparency, purpose limitation, data minimization, accuracy, storage limits, integrity, confidentiality, and accountability.
In practice, GDPR’s territorial scope expanded upon that of the e-Privacy Directive: any companies that offer goods or services, or monitor the behavior of individuals located in the EU must comply. When sanctioned, enforcement agencies have fined corporations thousands to several hundred million Euros for non-compliant activity. With this in mind, it’s important to consider how the utilization of client data in cookies needs to be compliant with new and upcoming legislature.
Cookies Across Browsers
Since 2017, Safari — Apple’s browser — began implementing its Intelligent Tracking Prevention to enhance client privacy. It initially restricted cross-site tracking third-party cookies, but later further expanded to require 7-day expiry for first-party cookies in Safari 12.1 in OS High Sierra and Mojave.
Mozilla’s Firefox has since followed suit, implementing Tracking Protection in Private Browsing and then expanding with Enhanced Tracking Protection in 2019 to block cookies from known third-party trackers. Now the default setting, it increases the difficulty for companies to track clients’ movements around the web.
One of the last major browsers to explore cookie limitations is Chrome. In early 2020, Google announced a phased approach to block third-party cookies over the next two years. Because some sites’ functionality and much of the existing third-party advertising is dependent upon third-party cookies, Chrome’s intention is to create new technical solutions to replace cookies so that site infrastructure won’t break while also balancing advertiser needs, potentially with anonymous tracking and targeting of demographics instead of unique individuals to ensure better client privacy.
That said, while each of the browsers have their own opinions on the best solution, there is, as of yet, no set solution. Potential options that some browsers have agreed to pursue include implementing privacy sandboxes instead of fingerprinting — a browser would allow data collection only up until a specific cutoff point — or potentially creating an API to track client conversions for e-commerce. Because cookies can be implemented in a wide variety of ways, a single solution to replace their usage is unlikely and it will take time to find solution commonality amongst the browsers.
Conclusion
As you can see, as the Internet continues to develop and standards change, new methodologies emerge to manage client state. Whether you prefer cookies or Web Storage, it is important to understand the pros and cons of each option. More information is available from the sources throughout and below for further learning beyond this introduction to cookies. Hopefully now you can more effectively determine what fits your own needs, and how you might implement cookies within your own web applications.
Good luck, and happy coding!
Sources
Internet Engineering Task Force: Standards Track 6265
MDN Web Docs: HTTP cookies, Document.cookie, XSS, CSRF, DNT, CORS
Flavio Copes: Learn how HTTP Cookies work
OWASP: Cross-Site Request Forgery Prevention Cheat Sheet
Privacy Policies: The Must-know Guide to the EU Cookie Directive
Law Office of Richard A Chapo: General Data Protection Regulation (GDPR)
The Mozilla Blog: Enhanced Tracking Protection by Default
Chromium Blog: Building a more private web
The Verge: Google to ‘phase out’ third-party cookies in Chrome, but not for two years