KO

EN

How to Secure Cookies

Cookies play a crucial role in web applications, but at the same time, they require careful attention to security settings. In this post, we'll take an in-depth look at various attributes to manage cookies more securely. We'll particularly explore how attributes like SameSite, along with HttpOnly, Secure, Path, Domain, Expires, and Max-Age, each contribute to bolstering cookie security.

Cookies can store sensitive information, such as session IDs that identify users. If these cookies are stolen by an attacker, there's a risk of session hijacking, allowing unauthorized access to user accounts. Furthermore, cookies can be exploited in Cross-Site Request Forgery (CSRF) attacks, leading users to perform unintended actions on a website. Therefore, it's critically important to understand each cookie attribute and configure them correctly.

Let's delve into the core security attributes that will help make your cookies robust.

1. HttpOnly: Protecting Against XSS

When the HttpOnly attribute is set, the cookie cannot be accessed via JavaScript's document.cookie API. This is effective in preventing attackers from stealing user cookie values (especially session IDs) through malicious scripts injected via Cross-Site Scripting (XSS) vulnerabilities on a website.

Example (HTTP Header):

Set-Cookie: session_id=verysecretvalue; HttpOnly

It's advisable to set the HttpOnly attribute for sensitive cookies, particularly those related to user authentication. While it's a fundamental defense mechanism, its effectiveness is significant.

2. Secure: Ensuring Encrypted Transmission

A cookie with the Secure attribute is only transmitted over an HTTPS connection. This means the cookie won't be sent over unencrypted HTTP connections, thereby protecting it from Man-in-the-Middle (MITM) attacks where an attacker might intercept the cookie on the network.

Example (HTTP Header):

Set-Cookie: user_preference=dark_mode; Secure

As most modern websites use HTTPS by default, applying the Secure attribute is also recommended. It's especially crucial to remember that if you use the SameSite=None policy, the Secure attribute must be set concomitantly.

3. SameSite: The CSRF Shield (In-Depth)

The SameSite attribute plays a pivotal role in defending against Cross-Site Request Forgery (CSRF) attacks. This attribute instructs the browser whether a cookie should be sent with requests initiated from other origins (cross-site). In simpler terms, it controls whether a cookie from your website (siteA.com) will be included in a request sent from an unrelated website (evil.com) to siteA.com.

The SameSite attribute can have one of three values:

  • Strict: This provides the strongest level of protection. Cookies set with SameSite=Strict are only sent if the request originates from the same site (a first-party context) where the user is currently interacting. For example, if a user clicks a link from an external site to yours, cookies set to Strict will not be sent. Consequently, if a login session cookie is set to Strict, the user might appear logged out when arriving via an external link and may need to log in again.

    • When to use: Suitable for cookies related to actions that change state, such as password changes, content creation, or order processing.
    • Example: Set-Cookie: session_id=verysecretvalue; SameSite=Strict
  • Lax: This is a slightly more relaxed setting than Strict but still offers excellent security. Lax generally behaves like Strict and doesn't send cookies on most cross-site requests. However, it does send cookies on cross-site requests if it's a top-level navigation using a safe HTTP method (GET, HEAD, OPTIONS, TRACE), such as when a user clicks a link to navigate to your site from an external one. This allows users to maintain their logged-in state when arriving from external links without compromising on CSRF protection for most scenarios. Many modern browsers have adopted Lax as the default for cookies where no SameSite attribute is specified.

    • When to use: Well-suited for general session management cookies, offering a good balance between security and user experience.
    • Example: Set-Cookie: tracking_id=randomstring; SameSite=Lax
  • None: With this setting, cookies are sent on all requests, both same-site and cross-site, similar to how cookies behaved before the SameSite attribute was introduced. However, a critical requirement is that if SameSite=None is used, the Secure attribute must also be specified (SameSite=None; Secure). This enforces that the cookie only operates over HTTPS connections, a measure to mitigate security risks.

    • When to use: Used sparingly, for cases where cookies are explicitly needed in a cross-site context, such as when your service is embedded within an iframe on an external service and requires cookie-based authentication, for Single Sign-On (SSO) scenarios spanning multiple domains, or for ad and analytics scripts.
    • Important: As this is the most flexible option, it also carries potential risks. Use it cautiously and only when absolutely necessary, and never omit the Secure attribute.
    • Example: Set-Cookie: third_party_widget_session=externalvalue; SameSite=None; Secure

Which SameSite value should you choose? If there's no specific reason otherwise, using SameSite=Lax as a default is a good practice. For cookies handling highly sensitive operations, SameSite=Strict can be considered. If cookies are absolutely required for cross-site requests, then SameSite=None; Secure is the appropriate approach.

The Path attribute restricts the URL path on the server to which the cookie will be sent. For instance, a cookie set with Path=/admin will only be sent for requests to /admin and its subdirectories (e.g., /admin/users). It will not be sent for requests to other paths like /dashboard or the root (/).

Example (HTTP Header):

Set-Cookie: admin_session_token=secretadminstuff; Path=/admin; HttpOnly; Secure

This helps minimize the cookie's exposure by preventing it from being unnecessarily transmitted to other application contexts. If not specified, the default path is the path of the document that set the cookie.

The Domain attribute specifies the host(s) to which the cookie will be sent. If set to Domain=example.com, the cookie will be sent for requests to example.com and all its subdomains (e.g., www.example.com, api.example.com). If the Domain attribute is omitted, the cookie is sent only to the exact host that set it (excluding subdomains).

Example (HTTP Header):

Set-Cookie: site_wide_preference=blue_theme; Domain=example.com

Overly broad Domain settings (e.g., to a TLD like .com) are a security risk and are rejected by browsers. Also, the Domain attribute can only be set to the current host's parent domain; it cannot be set to a completely different domain.

It's important to manage cookie persistence by setting an expiration time. Two attributes are used for this: Expires and Max-Age.

  • Expires: Specifies the exact date and time (in UTC) when the cookie will expire.

    • Example: Set-Cookie: legacy_cookie=data; Expires=Fri, 31 Dec 2025 23:59:59 GMT
  • Max-Age: Specifies the duration, in seconds, until the cookie expires. For example, Max-Age=3600 means the cookie is valid for one hour. Max-Age takes precedence over Expires. If Max-Age is set to 0 or a negative value, the cookie is deleted immediately.

    • Example: Set-Cookie: short_lived_token=tempdata; Max-Age=3600

If neither Expires nor Max-Age is set, the cookie is treated as a session cookie and is deleted when the browser is closed. However, as browser settings might restore sessions (and thus session cookies), it's a safer practice to set an explicit, short expiration time for sensitive information.

Conclusion: Layered Security is Key

We've explored various attributes for cookie security in detail: HttpOnly, Secure, SameSite, Path, Domain, Expires, and Max-Age. Understanding how each attribute works and its security benefits is crucial.

The core of cookie security lies in using these attributes appropriately in combination. No single setting can defend against all threats, so always consider a defense-in-depth strategy to protect your web applications and users.