What are Cookies? β HTTP State Management
After completing this topic, you will be able to:
Understand why HTTP is a "stateless" protocol, explain how cookies solve this problem, and know how to set and read cookies in Express.
HTTP Has No Memory
The HTTP protocol is stateless. After processing a request, the server completely forgets the client.
Client: GET /page1 β Server: "Here is the content of Page 1"
Client: GET /page2 β Server: "Who are you? Nice to meet you. Here is the content of Page 2"The server cannot tell if the browser that requested /page1 one second ago is the same user. Login, shopping carts, dark mode settingsβany feature that "remembers previous requests" is impossible due to this stateless nature.
Cookies solve this problem. It's like the server saying to the client, "Store this information and show it to me the next time you come."
How Cookies Work
Cookies are exchanged through HTTP headers.
1. Server β Client (response header)
Set-Cookie: username=Hoon; Path=/
2. Client β Server (next request's request header)
Cookie: username=HoonOnce set, the browser automatically includes the cookie in all requests to the same domain. The browser sends it without the server having to ask. This is the core mechanism of cookies.
Handling Cookies in Express
Setting Cookies
const express = require('express');
const app = express();
app.get('/login', (req, res) => {
// Send the Set-Cookie header
res.cookie('username', 'Hoon', {
maxAge: 24 * 60 * 60 * 1000, // 1 day (milliseconds)
httpOnly: true,
secure: false, // Only transmit over HTTPS (set to false during development)
sameSite: 'lax'
});
res.json({ message: 'Logged in, cookie set' });
});Reading Cookies
To read cookies, you need the cookie-parser middleware.
npm install cookie-parserconst cookieParser = require('cookie-parser');
app.use(cookieParser());
app.get('/profile', (req, res) => {
const username = req.cookies.username;
if (!username) {
return res.status(401).json({ error: 'Not logged in' });
}
res.json({ message: `Welcome back, ${username}` });
});cookie-parser parses the Cookie header and creates a req.cookies object. Without this middleware, you would have to parse the header yourself.
Deleting Cookies
app.get('/logout', (req, res) => {
res.clearCookie('username');
res.json({ message: 'Logged out, cookie cleared' });
});clearCookie immediately expires the cookie with the same name.
Cookie Attributes
| Attribute | Description | Example |
|---|---|---|
maxAge | Lifetime (in milliseconds). Cookie is automatically deleted after this time | 86400000 (1 day) |
expires | Expiration date (Date object). Less commonly used than maxAge | new Date('2026-12-31') |
httpOnly | Cannot be accessed by JavaScript (key for XSS protection) | true |
secure | Only transmitted over HTTPS connections | true |
sameSite | Whether to include the cookie in requests from other sites | 'strict', 'lax', 'none' |
path | Cookie is only transmitted for paths below this path | '/' |
domain | Domain for which the cookie is valid | '.example.com' |
Of these attributes, httpOnly and secure are the most important for security.
Security β Keeping Cookies Safe
httpOnly
// Bad β Cookie can be accessed by JavaScript (XSS vulnerability)
res.cookie('token', 'abc123');
// Good β Cannot be accessed by JavaScript
res.cookie('token', 'abc123', { httpOnly: true });Setting httpOnly: true prevents reading the cookie with document.cookie. Even if an XSS attacker injects a script, they cannot steal the cookie. This should always be set for authentication-related cookies.
secure
res.cookie('token', 'abc123', {
httpOnly: true,
secure: true // Only transmit over HTTPS
});The cookie is not transmitted over HTTP (unencrypted) connections. An attacker cannot intercept the cookie.
sameSite
res.cookie('token', 'abc123', {
httpOnly: true,
secure: true,
sameSite: 'strict' // Only transmit on requests from the same site
});| Value | Behavior |
|---|---|
'strict' | Cookie is included only in requests from the same site. Completely blocks CSRF. However, the user will be logged out if they navigate to the site from an external link. |
'lax' | Allows GET requests (link clicks) but blocks POST requests. A compromise between CSRF protection and usability. Recommended. |
'none' | Included in all requests. Requires secure: true. Only used for cross-site APIs. |
Cookies vs. Local Storage
| Cookie | localStorage | |
|---|---|---|
| Sent to server | Automatically included in every request | Not sent |
| Size | ~4KB | ~5MB |
| Expiration | Can expire automatically | Only deleted manually |
| Access | Cannot be accessed by JavaScript if httpOnly is set | Only accessible by JavaScript |
| Use case | Authentication, sessions, server settings | UI state, cache, offline data |
Use cookies for "information that the server needs to know," and localStorage for "information that only the browser needs to know."
Real-world pattern β Dark mode cookie
// Setting dark modeβcan be applied during server-side rendering
app.post('/settings/theme', (req, res) => {
const { theme } = req.body; // 'dark' or 'light'
res.cookie('theme', theme, {
maxAge: 365 * 24 * 60 * 60 * 1000, // 1 year
httpOnly: false, // JavaScript access required to apply CSS
sameSite: 'lax'
});
res.json({ theme });
});
app.get('/', (req, res) => {
const theme = req.cookies.theme || 'light';
res.render('index', { theme });
});For settings that are not security-sensitive, such as dark mode, you can set httpOnly: false. However, you should never set httpOnly: false for authentication tokens.
Key Takeaways
| Concept | Summary |
|---|---|
| HTTP Stateless | The server does not remember previous requests |
| Cookie | A small piece of data that the server asks the client to store |
| Set-Cookie | Server β Client (response header) |
| Cookie | Client β Server (request header, automatic) |
| httpOnly | Prevents JavaScript access (XSS protection) |
| secure | Only transmitted over HTTPS |
| sameSite | CSRF protection (lax recommended) |
Cookies are one of the oldest state management mechanisms on the web, but they are still widely used. Sessions, authentication, user settingsβall of these are based on cookies. If you forget to set the security attributes (httpOnly, secure, sameSite), you will be vulnerable to XSS and CSRF attacks, so always check these three when setting cookies.
Production Checklist
When setting authentication cookies, be sure to check these items:
res.cookie('session_token', token, {
httpOnly: true, // β XSS protection β Prevents JavaScript access
secure: true, // β HTTPS only β Prevents plain text transmission
sameSite: 'lax', // β CSRF protection β Blocks cross-site POST requests
maxAge: 3600000, // β Set expiration β Prevents persistent cookies
path: '/' // β Restrict path β Limit to necessary scope
});If you miss any of these, it will create a security vulnerability. Be sure to check this checklist before deploying to production.