HTTP cookie

  • key value pair stored by UA for a website
  • UA sends cookie for every subsequent request to same website
  • sent for specified URLs (on same domain), see Domain attribute and Path attribute
  • stored for specified duration, see Expires attribute and Max-Age attribute
  • used to maintain session across multiple request, makes HTTP stateful, e.g. authentication, tracking, etc.
  • beware: don’t confuse with cache, cache stores resources (with URL) and is not sent back to server ❗️
  • beware: store only data that server needs to know with every request, e.g. authentication, otherwise use local storage like Web Storage or IndexedDB, e.g. preferences (theme, language, banner dismissed) ⚠️
  • beware: never rely on existence, may stop to exist at any point in time, e.g. space constraint, switching browser, user deletes it, etc. ❗️
  • beware: never trust since can be manipulated like any other user input, always sanitise content and validate, e.g. malicious UA could send expired cookie, etc. ❗️
  • beware: never store sensitive data of user or server since can be extracted, instead store on server ❗️
  • beware: store as little data as possible since increases size of requests ❗️
  • beware: corresponds to single browser instance not single user, since single browser may be used by multiple users or single user may use multiple browsers ❗️
  • beware: sent for any kind of requests, e.g. user navigation, linked resource, fetch in JS, etc. ❗️
  • beware: origin domain of request does not need to be domain for which cookie is set, e.g. page with top-level navigation to external resource (e.g. link, popover, <link rel="prefetch">, etc.), page that loads external resource (e.g. image, iframe, etc.), page that fetches external resource through JS, etc. ❗️

Setting

  • can set via Set-Cookie response header(s)
  • Set-Cookie: <name>=<value>; <attributes>
  • name and value limited to certain characters, e.g. no white space, comma, semicolon, equal sign, etc.
  • can also set via JS document.cookie API
  • sent by UA in Cookie header, only name value pairs without attributes, multiple cookies separated by semicolon

Attributes

  • comma separated in header
  • beware: invalid attribute are silently ignored ❗️

Scope

  • URLs for which cookie is sent by UA
  • “current X” refers to X in URL of resource that set the cookie, e.g. current domain, current path, etc.

Domain

  • domain for which cookie is sent
  • can be current domain or super domain
  • must include current domain, e.g. auth.foo.com can set for foo.com but foo.com not for auth.foo.com
  • must not be another domain, e.g. foo.com can’t set cookie for bar.com
  • if not specified, defaults to current domain without sub domains
  • if specified, includes any sub domains
  • beware: if specified, is less restrictive than default ❗️

Path

  • path for which cookie is sent
  • can be any path
  • includes any sub paths, e.g. foo.com/ includes foo.com/blog/
  • beware: doesn’t need to include current path, e.g. foo.com/blog/ can set for foo.com/ ❗️
  • if not specified, defaults to current path

Duration

  • time for which cookie is stored by UA
  • defaults to end of session
  • beware: session doesn’t necessarily mean when UA is closed, e.g. restore session, etc. ❗️
  • calls “session cookie” if default duration, otherwise “persistent cookie”
  • beware: persistent cookie might live shorter than session cookie if duration is shorter than duration of session ❗️
  • can only set either Expires or Max-Age, if both then Max-Age takes precedence

Expires

  • HTTP-date timestamp
  • if in past, expires immediately

Max-Age

  • number of seconds from when resource was received
  • if non-positive, expires immediately

Security

  • mitigate attacks, e.g. XSS, man-in-the-middle, CSRF, etc.
  • beware: doesn’t make cookie itself secure, data can still be manipulated by user ⚠️

Secure

  • cookie is sent only for HTTPS requests
  • boolean attribute
  • beware: should always use ❗️
  • beware: should have been default, and Insecure be option ❗️
  • mitigates man-in-the-middle attack

HttpOnly

  • cookie can not be accessed through JS
  • boolean attribute
  • beware: should always use ❗️
  • beware: should have been default, and JsAlso be option ❗️
  • beware: still sent for requests originating from JS ❗️
  • mitigates XSS attack

SameSite

  • cookie is sent only for requests from certain origins (domains)
  • can set to Strict, Lax, or None
  • defaults to Lax
Strict
  • same-origin requests, i.e. no state when coming from Google, needs to reload page, etc.
  • mitigates CSRF
Lax
  • like Strict, but also for cross-origin request through top-level navigation
  • mitigates CSRF only partially, since can still make popover, <link rel="prefetch">, etc.
None
  • cross-origin requests, i.e. like previously
  • doesn’t mitigate CSRF
  • first-party cookie: cookie set by own resource (of same domain as domain of website)
  • third-party cookie: cookie set by foreign resource (of other domain than domain of website), e.g. external ads, external images, etc.
  • beware: first-/third-party cookie is relative to what website is currently visiting ❗️
  • third party recieves its cookie on every request for its resources from any page (given it uses SameSite=None)
  • third party can use to keep session accross websites, e.g. YouTube in embedded iframe
  • third party can use to track user accross websites
  • beware: don’t confuse with scope, each party can still only see their own cookies
  • UAs will block third party cookies by default, use Storage Access API instead

Regulations

  • cookies are regulated in many countries, e.g. limited duration, etc.
  • needs user’s constent except when “strictly necessary”

Resources