# Secure Cookie Attributes and CSRF — Authentication & Authorization

Source: https://www.skillbyai.com/en/authentication/s-cookies

> Secure, HttpOnly, SameSite and anti-CSRF tokens.

## Cookie flags that matter

**Secure** sends the cookie only over HTTPS. **HttpOnly** hides it from JavaScript, so an XSS bug cannot simply read it (XSS can still make requests as the user). **SameSite** controls cross-site sending: `Strict` never sends it on cross-site requests, `Lax` sends it on top-level GET navigations only, `None` always sends it and requires `Secure`. The **`__Host-`** name prefix makes browsers reject the cookie unless it is Secure, has `Path=/` and no `Domain`, which blocks subdomains from overwriting it. **CSRF** (cross-site request forgery) abuses the fact that browsers attach cookies automatically: SameSite helps a lot, but OWASP still recommends a defence such as the **synchronizer token** or **signed double-submit cookie** pattern for state-changing requests, plus checking `Origin`/`Sec-Fetch-Site` headers.

## A hardened Set-Cookie and a CSRF check

The header the browser receives, then a minimal synchronizer-token check.

```typescript
// Set-Cookie: __Host-sid=k3J...; Path=/; Secure; HttpOnly; SameSite=Lax; Max-Age=28800

// Issue a per-session CSRF token and embed it in forms / expose it to the SPA
app.get('/csrf', requireUser, (req, res) => {
  req.session.csrf ??= crypto.randomBytes(32).toString('base64url');
  res.json({ csrfToken: req.session.csrf });
});

// Verify on every state-changing request
function requireCsrf(req: Request, res: Response, next: NextFunction) {
  if (['GET', 'HEAD', 'OPTIONS'].includes(req.method)) return next();
  const sent = Buffer.from(String(req.get('x-csrf-token') ?? ''));
  const expected = Buffer.from(String(req.session.csrf ?? ''));
  if (sent.length === 0 || sent.length !== expected.length ||
      !crypto.timingSafeEqual(sent, expected)) {
    return res.status(403).json({ error: 'CSRF check failed' });
  }
  next();
}
```

## Keep GET requests safe

SameSite=Lax still sends cookies on top-level GET navigations, so a GET that changes state (such as /delete?id=5) remains vulnerable. Only use POST, PUT, PATCH or DELETE for changes.

**Quiz:** What does the HttpOnly attribute do?

- [ ] Blocks the cookie on all cross-site requests
- [ ] Forces the cookie to be sent only over HTTPS
- [x] Prevents page JavaScript from reading the cookie
- [ ] Encrypts the cookie value

*Answer:* Prevents page JavaScript from reading the cookie. HttpOnly limits cookie theft through XSS; Secure is the HTTPS-only flag.
