Web Security: How to Harden your HTTP Cookies
As a web developer, properly securing the sensitive data stored in your website‘s cookies is absolutely critical. HTTP cookies are a fundamental mechanism that enables websites to maintain sessions, store preferences, and personalize user experiences. However, the data contained in cookies is often a prime target for attackers, as compromised cookies can allow an attacker to impersonate users and access their private data and functionality.
In this guide, we‘ll take an in-depth look at how cookies work, the attributes available for fine-tuning and restricting cookies, and most importantly, the critical security flags you should be using to harden your cookies against common web attacks. By the end, you‘ll have a solid understanding of cookie security best practices that you can apply in your own web applications. Let‘s dive in!
HTTP Cookie Basics
At their core, HTTP cookies are small pieces of data that a web server sends to the user‘s browser. The browser then stores this data and automatically includes it in subsequent requests back to the same server. Cookies allow otherwise stateless HTTP servers to associate multiple requests from the same client and maintain session state.
Here‘s an example of how a server can set a cookie in an HTTP response using the Set-Cookie header:
HTTP/1.1 200 OK
Set-Cookie: session_id=abc123; Max-Age=3600; Secure; HttpOnly
The browser will then include this cookie in future requests to the server:
GET /account HTTP/1.1
Host: www.example.com
Cookie: session_id=abc123
While cookies are most commonly used to store session tokens or user preferences, they can contain any arbitrary data the server wants the browser to persist. It‘s this ability to store sensitive data that makes properly securing cookies so important.
Restricting Cookie Scope
Beyond just the cookie data itself, servers can also include several attributes to specify metadata about the cookie. These let you restrict the scope and lifetime of a cookie.
The Expires
and Max-Age
attributes define when the browser should delete the cookie. Expires
takes a specific date, while Max-Age
takes a number of seconds from when the browser receives the cookie:
Set-Cookie: session_id=abc123; Expires=Fri, 31 Dec 2023 23:59:59 GMT
Set-Cookie: session_id=abc123; Max-Age=2592000
Omitting both attributes creates a session cookie, which the browser deletes when it shuts down. This is generally preferable for session data, as you usually don‘t want those cookies to persist after the user closes their browser. Setting an expiry creates a persistent cookie which remains until the expiry time passes.
The Domain
and Path
attributes let you restrict which hostnames and URL paths the cookie will be sent to:
Set-Cookie: session_id=abc123; Domain=example.com
Set-Cookie: session_id=abc123; Path=/account
The browser will only include the cookie in requests to matching domains/paths. If unspecified, the browser defaults to the current hostname and path as a prefix. Explicitly setting Domain
to the current hostname creates a host-only cookie that won‘t be sent to any subdomains.
Trying to set a cookie for a top-level domain like .com
(called a supercookie) is ignored by browsers, as that would allow widespread user tracking across websites. However, creative techniques like cache ETags can still enable cross-site user tracking – these are also sometimes referred to as supercookies.
Critical Cookie Security Flags
While restricting cookie scope is useful, there are three key security attributes that should be your top priority when hardening cookies with sensitive data:
Secure
The Secure
flag instructs browsers to only send the cookie over HTTPS encrypted connections, never plain HTTP. This prevents an attacker from being able to intercept the cookie contents over an insecure network.
Set-Cookie: session_id=abc123; Secure
Any cookies containing sensitive data like session tokens should always be marked as Secure
. Even better, serve your entire site over HTTPS and ensure all cookies are Secure
by default.
HttpOnly
The HttpOnly
flag prevents JavaScript running on the page from being able to access the cookie using the document.cookie
API. This is a critical defense against cross-site scripting (XSS) attacks, where an attacker is able to inject malicious JavaScript that steals the user‘s cookie data.
Set-Cookie: session_id=abc123; HttpOnly
Always mark any sensitive cookies as HttpOnly
unless you explicitly need JavaScript access to the cookie contents. Blocking JavaScript access to session tokens goes a long way in limiting the damage of any potential XSS bugs.
SameSite
The SameSite
attribute is a relatively recent addition (2016) to browsers, but is quickly becoming a critical tool in preventing cross-site request forgery (CSRF) attacks.
Setting SameSite=Strict
prevents the cookie from being included in any requests initiated from another site. This blocks CSRF attacks where the attacker tries to make a victim‘s browser submit a forged request to your site‘s URLs.
Set-Cookie: session_id=abc123; SameSite=Strict
SameSite=Lax
is a slightly more permissive mode that still blocks most CSRF, but allows the cookie for top-level navigations like clicking a link. This provides better UX for things like external login links.
Set-Cookie: session_id=abc123; SameSite=Lax
While you should still include CSRF tokens in sensitive transactions as a fallback, SameSite
cookies are quickly becoming the primary recommended defense against CSRF.
Cookie Alternatives
With all the potential pitfalls of cookies, you might wonder if there are more secure alternatives. However, cookies remain the most compatible way to persist data across requests.
Local Storage (via JavaScript‘s localStorage
API) is sometimes used to store access tokens, particularly in single-page apps. However, any data in Local Storage is fully accessible to JavaScript, making it vulnerable to XSS. Sensitive data is better kept in HttpOnly
cookies.
JSON Web Tokens (JWTs) are a popular format for access tokens, but JWTs are just a token format – they can still be stored insecurely (like in Local Storage). JWTs are often used for session tokens, but should be stored in Secure
, HttpOnly
cookies to provide proper defenses.
Cookie Security Checklist
To wrap things up, here‘s a checklist you can follow to ensure you‘re properly hardening your cookies:
-
Only include sensitive data in cookies when absolutely necessary. Keep cookie sizes small.
-
Always serve sensitive cookies over HTTPS and set the
Secure
flag to prevent leaking data over insecure connections. -
Set the
HttpOnly
flag unless you explicitly need JavaScript access to a cookie. Keeping session cookiesHttpOnly
is a key defense against XSS attacks. -
Set the
SameSite
attribute toStrict
(orLax
ifStrict
breaks UX) to prevent CSRF attacks. Include CSRF tokens as a fallback for older browsers. -
Be mindful of cookie expiry times. Use session cookies for sensitive data to prevent persistence after browser close. Set reasonable
Max-Age
on long-lived cookies. -
Specify the
Domain
andPath
attributes to limit the scope of sensitive cookies. Use host-only cookies when possible. -
Rotate session tokens on any privilege level change and regularly regenerate tokens to limit session lifetimes.
-
Protect cookie data server-side by using encryption/signatures to detect tampering, and follow secure coding practices to prevent leaking cookie data in vulnerabilities like XSS, CSRF, etc.
By following this advice and staying aware of the latest cookie security best practices, you can ensure that your users‘ sensitive data remains well protected against cookie-based attacks. Understanding how to properly harden cookies is a critical skill for any security-minded web developer.