How do I fix a cookie that is missing the HttpOnly flag?
A session cookie with no HttpOnly attribute is readable from JavaScript, which means a single cross-site scripting bug is enough to steal the token. A cookie missing the HttpOnly flag is one of the most common findings in web app reviews, and the fix is usually a one-line change at the point the cookie is created. This page explains exactly what HttpOnly protects against and how to set it in Nginx, Apache, Express, and Next.js.
Why this matters
The HttpOnly attribute forbids JavaScript from accessing the cookie through document.cookie (MDN Set-Cookie). It does not stop the browser sending the cookie on JavaScript-initiated requests such as fetch() or XMLHttpRequest, so it is not a substitute for HTTPS or SameSite — its job is narrowly to mitigate cross-site scripting. OWASP's Session Management Cheat Sheet calls HttpOnly mandatory to prevent the theft of a session ID through XSS. The protection matters because XSS is both common and often persistent: if an attacker can run script on your origin, everything readable from document.cookie is compromised. HttpOnly raises that bar by keeping the cookie out of reach of client-side script while the browser still sends it automatically, so your server keeps authenticating the request. One deliberate exception exists: a cookie a script is supposed to read, such as the client-side half of a signed double-submit CSRF scheme, must not carry HttpOnly, and OWASP's CSRF Prevention Cheat Sheet shows setting that cookie without the HttpOnly flag. Everything else that authenticates a user should have it.
How to fix it
Add HttpOnly to every cookie that authenticates a user, and add it at the layer that creates the cookie so no code path can forget it. Nginx's proxy_cookie_flags adds httponly to cookies returned by an upstream proxy, so it is useful when the app itself is not under your control. Express and Next.js both take an httpOnly boolean in their cookie options. The snippets below come from each vendor's documentation.
location / {
proxy_pass http://app:3000;
proxy_cookie_flags ~ secure httponly samesite=lax;
}
res.cookie('session', token, {
httpOnly: true,
secure: true,
sameSite: 'lax'
});
'use server'
import { cookies } from 'next/headers'
export async function setSession(token) {
const cookieStore = await cookies()
cookieStore.set('session', token, {
httpOnly: true,
secure: true,
sameSite: 'lax',
path: '/'
})
}
Apache's mod_headers has no dedicated cookie-flag directive, so the documented edit action is used to search-and-replace the Set-Cookie value. Cloudflare Workers lets you append a full Set-Cookie response header, and its documented example already includes HttpOnly.
Header edit Set-Cookie ^(.*)$ "$1; HttpOnly"
const headers = new Headers();
headers.append("Set-Cookie", "session=" + token + "; Path=/; HttpOnly; Secure; SameSite=Lax");
return new Response(body, { headers });
How to verify the fix
Request the endpoint that sets the cookie and read the raw response header. HttpOnly must appear on the Set-Cookie line. In a browser, open DevTools, go to Application, then Cookies, and confirm the HttpOnly column is checked for the session cookie.
curl -sI https://example.com/login | grep -i "set-cookie"
Automated check
App Readiness Auditor requests your site, parses every Set-Cookie header it receives, and reports any cookie without the HttpOnly attribute as "Cookie missing HttpOnly flag: <name>" at Low severity. The report names the cookie so you can find the code that set it. Because the scanner cannot know which cookies are intentionally script-readable, treat each finding as a prompt to confirm the cookie is not the client half of a double-submit CSRF scheme before removing the exception.
Scan your app free →