HTTP cookies are the primary mechanism for maintaining authenticated sessions on the web. Their security depends almost entirely on four attributes: Secure, HttpOnly, SameSite, and the Domain scope. None of these attributes are set by default. Every cookie that lacks them is a cookie that is more exposed than it needs to be.
This article covers what each attribute does, what the concrete risk is when it is missing, how the attributes interact, and how to verify your application is setting them correctly.
Why Cookie Attributes Matter
A session cookie is a credential. If an attacker obtains a valid session cookie, they can impersonate the user for the lifetime of that session. The three main ways a session cookie is stolen or abused are: interception over an unencrypted connection, theft via JavaScript on a compromised or XSS-vulnerable page, and submission in cross-site requests (CSRF). The Secure, HttpOnly, and SameSite attributes defend against each of these attack paths respectively.
Because these attributes are not set automatically, they require explicit application configuration. The result is that many production applications set session cookies that are partially or fully unprotected, often because the framework default is to omit the attributes rather than include them.
The Secure Flag
The Secure flag instructs the browser to only send the cookie over HTTPS connections. A cookie without the Secure flag on an HTTPS site will be transmitted over plain HTTP if the browser makes any HTTP request to the same domain, including due to a protocol-downgrade attack, a cached HTTP link, or an HTTP sub-resource reference.
# Cookie without Secure flag — transmitted over HTTP
Set-Cookie: session=abc123; Path=/; HttpOnly
# Cookie with Secure flag — HTTPS only
Set-Cookie: session=abc123; Path=/; HttpOnly; SecureThe Secure flag only prevents transmission over unencrypted connections. It does not encrypt the cookie value, prevent JavaScript from reading it (that is HttpOnly's job), or restrict cross-site submission (that is SameSite's job).
For HTTPS sites, the Secure flag should be present on all cookies that contain any form of session identifier or authentication credential. Non-sensitive preference cookies (dark mode, locale) can omit it without meaningful security impact, but session-related cookies should always include it.
The HttpOnly Flag
The HttpOnly flag prevents JavaScript from accessing the cookie via document.cookie. When the flag is set, the browser sends the cookie in HTTP requests but does not expose it to the JavaScript environment. XSS attacks that attempt to steal the cookie by reading document.cookie will not find it.
# Cookie accessible to JavaScript — vulnerable to XSS theft
Set-Cookie: session=abc123; Path=/; Secure
# Cookie not accessible to JavaScript
Set-Cookie: session=abc123; Path=/; Secure; HttpOnlyHttpOnly does not prevent all XSS impact. An attacker with JavaScript execution on the page can still perform actions on behalf of the user (make authenticated requests, change email address, exfiltrate visible page content) without needing to steal the cookie itself. However, it does prevent the cookie from being exfiltrated to an attacker-controlled server, which removes the ability to replay the session from a different machine.
HttpOnly is appropriate for all session cookies and authentication tokens. Cookies that legitimately need to be read by JavaScript (for example, a CSRF token read by client-side code that appends it to request headers) should not have HttpOnly set. Those cookies should not contain session credentials.
SameSite: Lax, Strict, and None
The SameSite attribute controls whether the browser sends the cookie in cross-site requests. Without SameSite, every request to your domain from any other site (links, forms, image tags, iframes) includes your cookies, which is the condition CSRF attacks exploit.
The three values and their behavior:
SameSite=Strict: The cookie is never sent in cross-site requests, including top-level navigation (clicking a link from another site). This provides the strongest protection but can break flows where a user arrives at your site from an external link and expects to be recognized as logged in.SameSite=Lax: The cookie is sent on top-level navigation (clicking a link) but not on cross-site sub-resource requests (embedded images, forms, iframes). This is the browser default as of Chrome 80 when no SameSite attribute is set. It provides meaningful CSRF protection while preserving normal navigation behavior.SameSite=None: The cookie is sent in all cross-site requests. Required for cookies that must be sent in third-party contexts (embedded widgets, OAuth flows, payment iframes). Must be combined withSecure.
# Session cookie — Strict for maximum isolation
Set-Cookie: session=abc123; Path=/; Secure; HttpOnly; SameSite=Strict
# Auth cookie — Lax allows top-level navigation
Set-Cookie: auth=xyz789; Path=/; Secure; HttpOnly; SameSite=Lax
# Third-party context (embedded widget) — None requires Secure
Set-Cookie: widget_state=val; Path=/; Secure; SameSite=NoneSameSite=None Requires Secure
Modern browsers require cookies with SameSite=None to also carry the Secure flag. A cookie with SameSite=None but without Secure is rejected by Chrome, Firefox, and Edge. The intent is to prevent cross-site cookies from being sent over unencrypted connections. If your application sets SameSite=None without Secure, the cookie will silently not be sent by modern browsers, which breaks whatever cross-site functionality it was intended to support.
SameSite as a CSRF Defense
SameSite=Lax or SameSite=Strict on session cookies is an effective CSRF defense for the majority of applications. CSRF attacks work by triggering authenticated requests from a victim's browser on a page the attacker controls. If the session cookie has SameSite=Lax, the browser will not include it in cross-site POST requests, which are the typical CSRF attack vector.
SameSite does not replace CSRF tokens entirely. Applications that rely on third-party embedding, OAuth callback flows, or cross-origin API access may need SameSite=None and should use explicit CSRF tokens alongside it. For applications that operate only on their own origin, SameSite=Lax on session cookies provides strong CSRF protection.
Domain Scoping and Subdomain Exposure
The Domain attribute on a cookie controls which subdomains receive it. A cookie set with Domain=.example.com (note the leading dot, which is the standard form) is sent to all subdomains of example.com, including sub.example.com, api.example.com, and any other subdomain.
This creates an exposure risk: if any subdomain of example.com has an XSS vulnerability, a subdomain takeover vulnerability, or is compromised in any way, the attacker can read any cookie scoped to the parent domain. A session cookie intended only for the main application is exposed to every subdomain.
The correct approach is to set cookies without a Domain attribute when the cookie is only needed on the specific host that sets it. A cookie without an explicit Domain attribute is only sent to the exact host that set it, not to any subdomain.
# Sent to sub.example.com, api.example.com, and all other subdomains — broad scope
Set-Cookie: session=abc; Domain=.example.com; Secure; HttpOnly
# Sent only to the exact host that set it — narrow scope (no Domain attribute)
Set-Cookie: session=abc; Secure; HttpOnlyPersistent Session Cookies
A session cookie without an Expires or Max-Age attribute is a browser-session cookie: the browser deletes it when the tab or window is closed. A cookie with either of those attributes persists on disk until the specified time.
Session cookies with Max-Age values of 30 days or more represent a long window of exposure. If the user's device is accessed by another person, or if the cookie is stolen, it remains valid for the entire persistence window. The appropriate persistence duration depends on the application: banking and healthcare applications should use short-lived sessions (minutes to hours), while general applications often use 7 to 30 days.
Server-side session invalidation is separate from cookie persistence. A long-lived cookie on the client side is only usable for as long as the server considers the session valid. Implementing server-side session revocation on logout, password change, and suspicious activity is the complement to appropriate cookie persistence settings.
Verifying Cookie Attributes
Check the cookies set by your application with curl:
# Show Set-Cookie headers from the login or auth endpoint
curl -sI -X POST https://example.com/login -d "username=test&password=test" | grep -i set-cookie
# Or check the root path response cookies
curl -sI https://example.com/ | grep -i set-cookieIn browser developer tools, open the Application tab (Chrome) or Storage tab (Firefox), navigate to Cookies, and inspect each cookie. The columns show whether Secure, HttpOnly, and SameSite are set, along with the Domain scope and expiry.
Check every path that sets cookies, not just the root. Authentication endpoints, API responses, and third-party SSO callbacks may all set cookies that need separate review.
How WebDefect Checks Cookie Security
WebDefect fetches the target URL and collects all Set-Cookie headers from the response. For each cookie, the scanner evaluates:
- Secure flag (
cookie-001): Missing Secure flag on an HTTPS site is flagged as high severity for session cookies, medium for others. - HttpOnly flag (
cookie-002): Missing HttpOnly flag is flagged as medium severity for session-related cookies, low for others. - SameSite attribute (
cookie-003): Missing or absent SameSite is flagged as low severity.SameSite=Nonewithout credentials restriction is flagged separately. - SameSite=None without Secure (
cookie-004): This combination is flagged as high severity because modern browsers reject the cookie, breaking functionality, and it also implies cross-site delivery without encryption. - Domain scope breadth (
cookie-005): Cookies with aDomainattribute broader than the current host are flagged as medium severity for session cookies. - Persistent session cookies (
cookie-006): Session cookies with Max-Age exceeding 30 days are flagged as low severity.
Session cookie detection uses name pattern matching: cookies named session, sess, auth, token, sid, PHPSESSID, JSESSIONID, ASP.NET_SessionId, and similar patterns are treated as session-related and receive higher severity ratings for missing attributes.