Cookies¶
Cookies are not secrets. The browser stores them in plain text and every JavaScript running on the same origin can read them - unless you lock them down with flags
HTTP-only cookies prevent JavaScript access. Secure cookies prevent transmission over HTTP. SameSite cookies prevent CSRF. If you're not setting all three every time you set a cookie , you're doing it wrong
cookie-parser¶
Express 4.x doesn't parse cookies by default. You need cookie-parser
npm install cookie-parser
const cookieParser = require('cookie-parser')
app.use(cookieParser())
Now req.cookies is populated:
app.get('/' , (req , res) => {
console.log(req.cookies) // unsigned cookies
console.log(req.signedCookies) // signed cookies
res.json({ cookies: req.cookies })
})
setting cookies¶
// basic cookie
res.cookie('name' , 'mahmoud')
// cookie with all security options
res.cookie('session' , 'session-token-value' , {
httpOnly: true, // not accessible via JavaScript
secure: true, // only sent over HTTPS
sameSite: 'strict', // only sent for same-site requests
maxAge: 24 * 60 * 60 * 1000, // 24 hours in ms
path: '/', // cookie scope
domain: '.yourapp.com' // available to subdomains
})
signed cookies¶
Signed cookies prevent tampering. If someone modifies the cookie value , Express detects it
// need a secret
app.use(cookieParser('your-secret-here'))
// set signed cookie
res.cookie('session' , 'token-123' , {
signed: true, // sign this cookie
httpOnly: true,
secure: true
})
// read signed cookie
// req.cookies -> undefined (signed cookies not here)
// req.signedCookies -> { session: 'token-123' }
The cookie is signed with HMAC-SHA256. Tampering causes req.signedCookies to show the value as false
cookie options explained¶
res.cookie('name' , 'value' , {
httpOnly: true, // JS can't read it - prevents XSS cookie theft
secure: true, // only HTTPS - prevents MITM interception
sameSite: 'strict', // or 'lax' or 'none'
// 'strict' - only sent for same-site requests (best CSRF protection)
// 'lax' - sent for top-level GET navigations (balance)
// 'none' - sent cross-site (requires secure: true , needs Secure attribute)
maxAge: 3600000, // time in milliseconds
expires: new Date(Date.now() + 3600000), // alternative to maxAge
path: '/', // limit to specific path
domain: '.example.com', // limit to specific domain
signed: true // prevent tampering
})
cookie security¶
// MINIMUM SECURE CONFIG - every cookie should have these
res.cookie('sessionId' , token , {
httpOnly: true,
secure: true,
sameSite: 'strict'
})
// DO NOT store raw JWTs in cookies without these flags
// DO NOT set secure: false in production
// DO NOT use sameSite: 'none' unless you understand cross-site implications
// deleting cookies
res.cookie('sessionId' , '' , { maxAge: 0, httpOnly: true, secure: true })
// or
res.clearCookie('sessionId')
common cookie mistakes¶
// MISTAKE 1: No httpOnly - XSS can read cookies
res.cookie('session' , token) // BAD - JS can steal this
// MISTAKE 2: No secure flag on production
res.cookie('session' , token , { httpOnly: true }) // BAD over HTTP
// MISTAKE 3: No sameSite - CSRF attacks possible
res.cookie('session' , token , { httpOnly: true , secure: true }) // missing sameSite
// MISTAKE 4: Setting cookies after sending response
res.json({ ok: true })
res.cookie('session' , token) // TOO LATE - response already sent
// MISTAKE 5: Not signing auth cookies
res.cookie('userId' , '42' , { httpOnly: true }) // user can change to '1' and become admin
cookie size limits¶
Browsers enforce limits:
- 4KB per cookie - total size including name , value , and attributes
- ~50-150 cookies per domain - varies by browser
- ~4096 bytes for the entire Cookie header
If you need to store more data , use server-side sessions with a session ID stored in the cookie. That's what express-session does
cookie reading and parsing¶
const cookieParser = require('cookie-parser')
app.use(cookieParser('secret'))
app.get('/' , (req , res) => {
// all cookies (unsigned)
console.log(req.cookies)
// only signed cookies
console.log(req.signedCookies)
// check if a specific signed cookie is valid
if (req.signedCookies.session) {
// cookie is valid and untampered
} else if (req.cookies.session && !req.signedCookies.session) {
// cookie exists but was tampered with - possible attack detected
console.warn('Tampered cookie detected')
}
})
prerequisites¶
express_08_form_data.md - form data , multer , express-validator
next → express_10_sessions.md