Skip to content

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

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

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
})
// 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')
// 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

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

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