What Are Cookies in Web Development? A Practical Guide for AI-Assisted Developers
Cookies are how browsers remember you. When AI builds auth or sessions into your app, cookies are almost always involved — and the security flags matter a lot.
TL;DR
Cookies are small text files (max 4KB) that a server stores in the user's browser to persist data between requests. Unlike localStorage, cookies are automatically sent with every HTTP request to the server — which is why they're used for authentication sessions. Critical security flags: HttpOnly (prevents JS access), Secure (HTTPS only), SameSite (blocks CSRF). AI often generates cookies without these flags — always add them on auth cookies.
Why AI Coders Need to Know This
The moment you ask AI to "add user login," it will generate cookies. When you ask for "remember me," it sets a cookie. When it builds a shopping cart, a cookie. When your app keeps you logged in after closing the browser — cookie.
The problem: AI often generates working cookie code with missing security flags. A missing HttpOnly flag makes your session token vulnerable to JavaScript-based attacks. A missing Secure flag sends auth data over plain HTTP. These aren't theoretical risks — they're common vulnerabilities in AI-generated apps.
You don't need to memorize every cookie attribute. You need to know the three security flags and always check that AI includes them on any cookie that holds user data.
Real Scenario
Your prompt to the AI
"Build a login endpoint in Express.js. When a user logs in successfully, set a session cookie that keeps them logged in. Make sure it's secure."
The keyword "secure" in the prompt is important — it should trigger the AI to include security flags. Let's see what it produces.
What AI Generated
// Express.js login endpoint with secure cookie setup
const express = require('express');
const router = express.Router();
router.post('/login', async (req, res) => {
const { email, password } = req.body;
try {
// 1. Verify credentials (simplified)
const user = await authenticateUser(email, password);
if (!user) {
return res.status(401).json({ error: 'Invalid credentials' });
}
// 2. Create a session token
const sessionToken = generateSecureToken(); // crypto.randomBytes(32).toString('hex')
// 3. Store session in database
await saveSession(sessionToken, user.id, Date.now() + 7 * 24 * 60 * 60 * 1000); // 7 days
// 4. Set the cookie with security flags
res.cookie('session_token', sessionToken, {
httpOnly: true, // ← JS cannot read this cookie (XSS protection)
secure: true, // ← Only sent over HTTPS
sameSite: 'lax', // ← Blocks most CSRF attacks
maxAge: 7 * 24 * 60 * 60 * 1000, // 7 days in milliseconds
path: '/' // ← Cookie valid for all routes
});
res.json({ success: true, user: { id: user.id, email: user.email } });
} catch (error) {
console.error('Login error:', error);
res.status(500).json({ error: 'Login failed' });
}
});
// Logout: clear the cookie
router.post('/logout', (req, res) => {
res.clearCookie('session_token', {
httpOnly: true,
secure: true,
sameSite: 'lax'
});
res.json({ success: true });
});
// Read cookie in middleware
function requireAuth(req, res, next) {
const token = req.cookies.session_token; // requires cookie-parser middleware
if (!token) {
return res.status(401).json({ error: 'Not authenticated' });
}
// Validate token against database...
next();
}
module.exports = router;
Understanding Each Part
How cookies work: the flow
- Server sends: On login, the server sends a
Set-Cookieheader in the HTTP response. The browser receives and stores it. - Browser stores: The browser saves the cookie associated with that domain and path.
- Browser sends: On every subsequent request to that domain, the browser automatically includes the cookie in a
Cookieheader. You don't write any code to make this happen — it's automatic. - Server reads: The server reads
req.cookies.session_tokento verify the user's identity.
This is fundamentally different from localStorage, which the server never sees.
The HttpOnly flag
When set, JavaScript cannot access this cookie via document.cookie. This is critical for session tokens: if malicious code runs in your browser (XSS attack), it cannot steal the cookie because JS can't read it. Never skip this on auth cookies.
The Secure flag
The browser only sends this cookie over HTTPS connections. During local development (http://localhost), you may need to set this to false temporarily — but it must be true in production. AI sometimes hardcodes secure: false for development and doesn't change it before shipping.
The SameSite attribute
- Strict: Cookie never sent on cross-site requests. Most secure, but breaks some OAuth flows.
- Lax: Cookie sent on top-level navigation (clicking a link) but not on cross-site POST requests. Good default.
- None: Cookie sent on all requests. Required for cross-site cookies (like an embedded payment widget) but must be paired with
Secure.
maxAge vs. expires
maxAge sets the cookie lifetime in milliseconds from now. expires sets an absolute date/time. Without either, the cookie is a "session cookie" — it disappears when the browser closes. For "remember me" functionality, set maxAge.
Cookie size limit
Cookies are limited to 4KB per cookie. Don't store large amounts of data in cookies — store a session ID in the cookie and keep the real data in your database. This is what the code above does: the cookie holds only a token, and the token maps to the user session in the database.
What AI Gets Wrong
1. Missing security flags on auth cookies
The most dangerous omission. AI frequently generates res.cookie('token', value) without httpOnly: true or secure: true. Any session cookie without HttpOnly is vulnerable to XSS. Any cookie without Secure can be intercepted on HTTP.
Always audit this in any AI-generated authentication code.
2. Storing sensitive data in the cookie itself
AI sometimes puts user data (email, role, user ID) directly in the cookie value. This is backwards — the cookie should store only an opaque token, and the server looks up the real data. Storing data in cookies means it's exposed in the browser (even with HttpOnly, the server can read it on every request).
3. Setting secure: false in production
AI sometimes writes secure: process.env.NODE_ENV !== 'development' but forgets to set NODE_ENV=production in the deployment environment. The cookie goes live with secure: false. Check your environment variables when deploying.
4. Forgetting cookie-parser middleware
In Express.js, you can't read req.cookies without adding app.use(cookieParser()). AI generates the cookie reading code correctly but sometimes forgets to include the middleware setup, causing req.cookies to be undefined.
How to Debug with AI
Inspecting cookies in DevTools
Open DevTools (F12) → Application tab → Cookies → your domain. You'll see every cookie, its value, flags, and expiration. This is the fastest way to confirm your cookie was set and has the right flags.
Cookie not being sent to the server
Check: Does the cookie's domain/path match where you're making the request? Is Secure set but you're on HTTP (localhost)? Is SameSite=Strict blocking a cross-origin request?
Debugging prompt: "My session cookie isn't being sent to the server. I'm on http://localhost:3000 making requests to http://localhost:4000 (different port). The cookie has SameSite=Lax and Secure=false for dev. What would prevent it from being sent?"
Tool-specific tips
- Cursor: Ask it to add console.log statements that print
req.cookieson each protected route to confirm the cookie is arriving. - Claude Code: Paste your cookie configuration and ask: "Audit this cookie setup for security. Is HttpOnly set on auth cookies? Is Secure appropriate for my environment?"
- Windsurf: Ask it to create a security checklist for all cookie usage in the project and flag any cookies missing required security flags.
What to Learn Next
- What Is Authentication? — Cookies are the transport layer for session-based auth. Understanding authentication explains why cookies are structured this way.
- What Is JWT? — JWT is an alternative approach where the token itself carries the user data. Comparing JWT with session cookies helps you choose the right approach.
- What Is XSS? — The attack that HttpOnly protects against. Understanding the threat makes the defense make sense.
- What Is CSRF? — The attack that SameSite protects against.
Security Rule
Every auth cookie needs three flags: httpOnly: true, secure: true (in production), and sameSite: 'lax'. Check them every time AI generates a res.cookie() call.
FAQ
Cookies are small text files (up to 4KB) that a web server stores in the user's browser. They persist between page loads and browser sessions, allowing servers to identify returning users, maintain login state, and remember preferences. Cookies are automatically sent with every HTTP request to the matching domain.
Cookies are sent with every HTTP request to the server automatically, making them suitable for authentication tokens. localStorage stays in the browser only — the server never sees it. Cookies can expire and have security flags (HttpOnly, Secure, SameSite); localStorage has no built-in expiration or security flags.
The HttpOnly flag prevents JavaScript from reading the cookie via document.cookie. This protects session tokens from being stolen by XSS attacks, where malicious script tries to read and exfiltrate cookies. Always set HttpOnly on session and authentication cookies.
The Secure flag tells the browser to only send the cookie over HTTPS connections, never over plain HTTP. This prevents the cookie value from being transmitted in cleartext over insecure networks. Always set Secure in production on any cookie containing sensitive data.
SameSite controls whether cookies are sent on cross-site requests. Strict = only same-site requests. Lax = allows cookies on top-level navigations (the safe default). None = all cross-site requests (requires Secure). SameSite=Lax is the browser default in Chrome, Firefox, and Safari and protects against most CSRF attacks.