Introduction
Cookies are the cornerstone of state management on the web. They enable everything from user authentication to personalization, but their ubiquity also makes them a prime target for attackers. One of the most effective, yet often misunderstood, defenses against client‑side attacks is the HttpOnly flag. When correctly applied, HttpOnly can dramatically reduce the risk of session hijacking via cross‑site scripting (XSS) and other client‑side exploits.
In this article we will:
- Explain what the HttpOnly attribute is and how browsers enforce it.
- Explore the threat model it addresses and the attacks it mitigates.
- Show how to set HttpOnly in popular server‑side languages and frameworks.
- Compare HttpOnly with related cookie attributes such as
SecureandSameSite. - Provide practical testing, debugging, and monitoring strategies.
- Discuss common pitfalls, limitations, and future directions.
By the end, you’ll have a solid understanding of when and how to use HttpOnly, and you’ll be equipped to audit existing applications for proper cookie hygiene.
Table of Contents
- What Is the HttpOnly Flag?
- Why HttpOnly Matters: Threat Landscape
- How Browsers Enforce HttpOnly
- Setting HttpOnly in Different Environments
- HttpOnly vs. Secure vs. SameSite
- Testing and Verifying HttpOnly Cookies
- Common Pitfalls and Misconfigurations
- Advanced Topics
- Best‑Practice Checklist
- Conclusion
- Resources
What Is the HttpOnly Flag?
The HttpOnly attribute is an optional flag that can be attached to a cookie when it is sent from a server to a browser via the Set-Cookie HTTP response header. Its syntax is simple:
Set-Cookie: sessionId=abc123; HttpOnly; Path=/; Secure; SameSite=Strict
When the HttpOnly flag is present, browsers must not expose the cookie to client‑side scripts (i.e., JavaScript’s document.cookie API). The cookie remains accessible to the browser’s HTTP stack, meaning it will be automatically included in subsequent HTTP requests that match the cookie’s scope (domain, path, and secure attributes).
Key points:
- Server‑only access – Only the server can read/write the cookie via HTTP headers.
- No JavaScript read/write –
document.cookiewill not list HttpOnly cookies, nor can scripts set them. - Mitigation, not a silver bullet – HttpOnly reduces risk but does not replace proper input validation, CSP, or other defensive layers.
Why HttpOnly Matters: Threat Landscape
Cross‑Site Scripting (XSS)
XSS occurs when an attacker injects malicious JavaScript into a vulnerable web page. Once executed, the script runs with the same privileges as the legitimate page, gaining access to everything the page can see—including cookies via document.cookie. If a session cookie is exposed, the attacker can hijack the user’s session.
HttpOnly blocks the most common XSS exploitation path: stealing the session identifier from the client. While sophisticated XSS attacks can still perform actions on behalf of the user (e.g., CSRF via forged requests), they can no longer simply exfiltrate the cookie.
Quote: “HttpOnly is a critical line of defense that turns a client‑side script from a secret‑stealing tool into a mere browser automation script.” – OWASP
Session Hijacking
Session hijacking is the broader category of attacks that aim to take over a user’s authenticated session. With an HttpOnly cookie, an attacker cannot directly read the session token, but they may still:
- Perform session fixation – If the server accepts arbitrary session IDs.
- Exploit other client‑side vulnerabilities – Such as DOM‑based XSS that manipulates the page without needing the cookie value.
Thus, HttpOnly is a defense‑in‑depth measure, not a stand‑alone solution.
How Browsers Enforce HttpOnly
When a browser receives a Set-Cookie header containing the HttpOnly attribute, it stores the cookie in its internal cookie jar. The enforcement rules are:
| Action | Allowed? | Reason |
|---|---|---|
| Include cookie in an HTTP request that matches domain/path | ✅ | Part of the HTTP protocol |
Access cookie via document.cookie or any JavaScript API | ❌ | Flag instructs the browser to hide it from script contexts |
Modify cookie via Set-Cookie response from server | ✅ | Server-controlled |
Delete cookie via Set-Cookie: name=; expires=Thu, 01 Jan 1970 00:00:00 GMT | ✅ | Server-controlled |
Read cookie via XMLHttpRequest/fetch response headers | ❌ (unless server echoes it) | Browser does not expose Set-Cookie headers to scripts |
All major browsers (Chrome, Edge, Firefox, Safari) honor this flag. However, legacy browsers (e.g., Internet Explorer 6) did not support HttpOnly, which historically forced developers to rely on additional mitigations. Modern browsers have consistent behavior, making HttpOnly a reliable security control.
Setting HttpOnly in Different Environments
Below are practical examples for setting HttpOnly cookies across common server‑side stacks.
Node.js / Express
// app.js (Express 4.x)
const express = require('express');
const app = express();
app.post('/login', (req, res) => {
// Authenticate user...
const token = generateSessionToken(req.body.username);
// Set HttpOnly cookie
res.cookie('sessionId', token, {
httpOnly: true, // <-- HttpOnly flag
secure: true, // send only over HTTPS
sameSite: 'strict', // optional, see later section
maxAge: 24 * 60 * 60 * 1000 // 1 day
});
res.json({ success: true });
});
app.listen(3000, () => console.log('Server running on :3000'));
Notes:
res.cookieis part of thecookie-parsermiddleware or built‑in Express API.- Always combine
httpOnly: truewithsecure: truefor production to enforce transport security.
PHP
<?php
// login.php
session_start();
// After successful authentication:
$sessionId = bin2hex(random_bytes(32));
// Set HttpOnly cookie
setcookie(
'sessionId',
$sessionId,
[
'expires' => time() + 86400, // 1 day
'path' => '/',
'domain' => '', // current domain
'secure' => true, // HTTPS only
'httponly' => true, // <-- HttpOnly flag
'samesite' => 'Strict' // optional
]
);
?>
PHP 7.3+ supports the associative array syntax for setcookie. For older versions, you must manually construct the header:
header('Set-Cookie: sessionId=' . $sessionId . '; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=86400');
Java / Spring Boot
// LoginController.java
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.bind.annotation.*;
@RestController
public class LoginController {
@PostMapping("/login")
public String login(@RequestBody LoginRequest request,
HttpServletResponse response) {
// Authenticate user...
String token = TokenService.createToken(request.getUsername());
Cookie cookie = new Cookie("SESSIONID", token);
cookie.setHttpOnly(true); // <-- HttpOnly
cookie.setSecure(true); // HTTPS only
cookie.setPath("/");
cookie.setMaxAge(24 * 60 * 60); // 1 day
cookie.setSameSite("Strict"); // Spring 6+ (or via response header)
response.addCookie(cookie);
return "OK";
}
}
If you use Spring Security, you can configure the session cookie centrally:
http
.sessionManagement()
.sessionFixation().migrateSession()
.sessionCookie(cookie -> cookie
.httpOnly(true)
.secure(true)
.sameSite("Strict"));
Python / Django & Flask
Django
# settings.py
SESSION_COOKIE_HTTPONLY = True # Default is True
SESSION_COOKIE_SECURE = True # Recommended for production
SESSION_COOKIE_SAMESITE = 'Strict' # Optional
Django automatically adds the HttpOnly attribute to the session cookie if SESSION_COOKIE_HTTPONLY is True. For custom cookies:
def set_custom_cookie(response):
response.set_cookie(
key='my_token',
value=generate_token(),
httponly=True, # <-- HttpOnly
secure=True,
samesite='Lax',
max_age=86400
)
Flask
from flask import Flask, make_response, request
import secrets
app = Flask(__name__)
@app.route('/login', methods=['POST'])
def login():
# Authenticate user...
token = secrets.token_urlsafe(32)
resp = make_response({'status': 'ok'})
resp.set_cookie(
'sessionId',
token,
httponly=True, # <-- HttpOnly
secure=True, # HTTPS only
samesite='Strict',
max_age=24*60*60
)
return resp
Flask’s set_cookie API mirrors the underlying Werkzeug implementation.
ASP.NET Core
// Startup.cs (ConfigureServices)
services.ConfigureApplicationCookie(options =>
{
options.Cookie.HttpOnly = true; // <-- HttpOnly
options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
options.Cookie.SameSite = SameSiteMode.Strict;
});
Or manually in a controller:
public IActionResult Login()
{
var token = GenerateToken();
var cookieOptions = new CookieOptions
{
HttpOnly = true,
Secure = true,
SameSite = SameSiteMode.Strict,
Expires = DateTimeOffset.UtcNow.AddDays(1)
};
Response.Cookies.Append("sessionId", token, cookieOptions);
return Ok();
}
HttpOnly vs. Secure vs. SameSite
| Attribute | Purpose | Typical Use‑Case | Browser Support |
|---|---|---|---|
| HttpOnly | Prevents client‑side script access to the cookie. | Mitigate XSS‑based token theft. | All modern browsers (since ~2003). |
| Secure | Ensures cookie is sent only over HTTPS. | Prevent eavesdropping on unsecured connections. | All browsers; ignored on HTTP. |
| SameSite | Controls cross‑site request handling. Values: Lax, Strict, None. | Defend against CSRF; improve privacy. | Chrome 51+, Firefox 60+, Edge 79+, Safari 12+. |
These attributes are orthogonal; you should generally enable all three for session cookies:
Set-Cookie: sessionId=xyz; HttpOnly; Secure; SameSite=Strict
HttpOnlyprotects against script extraction.Secureprotects against network sniffing.SameSiteprotects against cross‑site request forgery.
Testing and Verifying HttpOnly Cookies
Browser Developer Tools
- Open Chrome DevTools (F12) → Application tab → Cookies.
- Locate your cookie; the HttpOnly column should read ✓.
- Switch to the Console and attempt:
document.cookie // HttpOnly cookies will NOT appear
If you see the cookie value, the flag is missing.
Automated Scanners
- OWASP ZAP – Use the “Active Scan” rule “Cookie HttpOnly flag not set”.
- Burp Suite – In the “Scanner” module, look for the “Missing HttpOnly flag” issue.
- Qualys WAS – Provides a “Cookie Security” audit.
Running these tools on staging environments helps catch misconfigurations before production.
Common Pitfalls and Misconfigurations
| Pitfall | Impact | How to Fix |
|---|---|---|
| Setting HttpOnly on non‑session cookies only | Leaves session cookie vulnerable. | Ensure all authentication‑related cookies have HttpOnly. |
| Forgetting Secure with HttpOnly | Cookie may be sent over HTTP, exposing it to network sniffing. | Always combine Secure with HttpOnly for production. |
Using SameSite=None without Secure | Modern browsers reject such cookies, breaking functionality. | When SameSite=None is needed (e.g., third‑party auth), also set Secure. |
| Overriding HttpOnly via JavaScript | Some frameworks expose helper functions that inadvertently clear the flag. | Review any client‑side cookie manipulation libraries. |
| Setting HttpOnly on sub‑domain cookies inconsistently | Attackers can target less‑protected sub‑domains. | Apply consistent policy across all sub‑domains, or use the Domain attribute cautiously. |
Advanced Topics
HttpOnly with Sub‑Domain Cookies
When you set a cookie with Domain=.example.com, it is shared across all sub‑domains (app.example.com, api.example.com). If any sub‑domain fails to enforce HttpOnly, an attacker could compromise the whole domain’s session token.
Best practice:
- Keep a single origin for authentication (e.g.,
auth.example.com). - Issue session cookies with
Domain=auth.example.com(no domain attribute) to limit scope. - Use SameSite=Lax/Strict to further restrict cross‑site usage.
Cookie Partitioning & First‑Party Isolation
Modern browsers (Safari’s Intelligent Tracking Prevention, Chrome’s “Partitioned Cookies”) are moving toward first‑party isolation, where cookies set in a third‑party context are stored separately per top‑level site. While this does not directly affect HttpOnly, it changes how cookies are shared across frames and can impact legacy applications that rely on third‑party session cookies.
Developers should:
- Audit third‑party integrations (e.g., embedded payment widgets).
- Prefer postMessage or OAuth redirects instead of shared cookies.
Future of HttpOnly: Emerging Standards
- **
HttpOnlyis being superseded by theCookieattributeHttpOnlyin the Cookie Store API for Service Workers, enabling explicit control over exposure. SecureandSameSiteare evolving with theCookieheaderPriorityattribute, influencing how browsers treat storage limits.Partitioned Cookiesmay affect how HttpOnly enforcement is scoped per top‑level site; developers need to stay aware of Chrome’s upcoming changes (expected 2026).
Best‑Practice Checklist
- Set
HttpOnlyon every authentication‑related cookie (session, refresh tokens, CSRF tokens). - Pair
HttpOnlywithSecurefor all production traffic. - Use
SameSite=Strictwhere possible; fallback toLaxfor navigation‑based flows. - Avoid setting cookies on the root domain unless absolutely necessary.
- Test with multiple browsers (Chrome, Firefox, Safari, Edge) and mobile equivalents.
- Run automated scans (OWASP ZAP, Burp) on every CI pipeline.
- Log cookie attributes (but never log the cookie value) for audit trails.
- Document cookie policy in your security guidelines and onboarding material.
Conclusion
The HttpOnly flag is a simple, widely supported, and highly effective tool in the web security arsenal. By preventing client‑side scripts from accessing sensitive cookies, it blocks one of the most common avenues for session hijacking via XSS attacks. However, it must be used in concert with Secure, SameSite, and a robust overall security posture that includes input validation, CSP, and regular vulnerability scanning.
Implementing HttpOnly correctly across different platforms is straightforward—most modern frameworks expose a single boolean or configuration option. Yet the real challenge lies in maintaining consistent policies across all services, sub‑domains, and environments, and ensuring that developers and DevOps teams understand the consequences of misconfiguration.
By following the guidelines, examples, and checklist provided in this article, you can confidently harden your web applications against client‑side cookie theft and move one step closer to a secure, privacy‑respecting user experience.
Resources
- OWASP Secure Coding Practices – Session Management Cheat Sheet
- MDN Web Docs – Set-Cookie
- Google Chrome Platform Status – Partitioned Cookies