# Learn how HTTP Cookies work

> Learn how HTTP cookies move between browser and server, how scope and expiration work, and how Secure, HttpOnly, and SameSite protect them.

Author: [Flavio Copes](https://flaviocopes.com/about/) | Published: 2018-03-30 | Updated: 2026-07-18 | Topics: [Web Platform](https://flaviocopes.com/tags/platform/) | Canonical: https://flaviocopes.com/cookies/

An HTTP cookie is a small name-value pair that a browser stores for a site.

Servers often use cookies to identify a session. The cookie contains an opaque session ID, while the server keeps the account and session data.

Cookies are also used for preferences and other small values. They are not a replacement for a database.

## How cookies travel

A server sets a cookie in an HTTP response:

```http
Set-Cookie: session=abc123; Path=/; Secure; HttpOnly; SameSite=Lax
```

The browser stores it. On later matching requests, the browser sends:

```http
Cookie: session=abc123
```

The browser decides whether a cookie matches by checking its domain, path, expiration, `Secure`, `SameSite`, and partition rules.

Frontend JavaScript does not need to manually add the `Cookie` header. Browsers manage that header, and do not let scripts set it directly.

## Cookie limits

Cookies are intentionally small. Browser limits vary, but an individual cookie is commonly limited to about 4 KB.

Browsers also limit how many cookies an origin or domain can keep. Do not depend on one exact number.

Cookies add bytes to matching HTTP requests. Use [Web Storage](https://flaviocopes.com/web-storage-api/) or [IndexedDB](https://flaviocopes.com/indexeddb/) for client-only data that does not need to reach the server.

## Session and persistent cookies

A cookie without `Expires` or `Max-Age` is a **session cookie**:

```http
Set-Cookie: theme=dark; Path=/
```

Session cookies normally last for the browser session. Browser session restore can preserve them across a restart, so closing the browser is not a reliable security boundary.

Use `Max-Age` to set a lifetime in seconds:

```http
Set-Cookie: theme=dark; Max-Age=2592000; Path=/
```

This cookie can last for 30 days.

Alternatively, use an HTTP date with `Expires`:

```http
Set-Cookie: theme=dark; Expires=Tue, 18 Aug 2026 12:00:00 GMT; Path=/
```

When both are present, `Max-Age` takes precedence.

## Limit a cookie by host and path

If you omit `Domain`, the browser creates a **host-only cookie**. A cookie set by `app.example.com` then returns only to `app.example.com`, not to its subdomains or parent domain.

Set `Domain=example.com` only when the cookie must also reach subdomains:

```http
Set-Cookie: preference=compact; Domain=example.com; Path=/
```

A site cannot set cookies for an unrelated domain or a public suffix such as `com`.

Leaving out `Domain` is the safer default because it gives the cookie a smaller scope.

`Path` limits which request paths receive the cookie:

```http
Set-Cookie: dashboard-view=list; Path=/dashboard
```

This matches `/dashboard` and paths below it.

`Path` is a delivery rule, not a security boundary. Other code on the same origin may still be able to access the cookie.

## Protect cookies with attributes

### Secure

`Secure` tells the browser to send the cookie only over HTTPS:

```http
Set-Cookie: session=abc123; Secure
```

Use HTTPS for the entire site. `Secure` protects cookie transport, but does not encrypt the value inside the browser or server.

### HttpOnly

`HttpOnly` hides a cookie from JavaScript APIs such as `document.cookie`:

```http
Set-Cookie: session=abc123; Secure; HttpOnly
```

Only a server can set `HttpOnly` through the `Set-Cookie` response header. This does not work:
adding `HttpOnly` to a `document.cookie` assignment cannot create an
`HttpOnly` cookie.

An XSS payload cannot read an `HttpOnly` session cookie. It may still perform actions as the signed-in user, so `HttpOnly` reduces impact but does not fix XSS.

### SameSite

`SameSite` controls whether a cookie is sent with cross-site requests.

`Strict` gives the narrowest cross-site behavior:

```http
Set-Cookie: session=abc123; SameSite=Strict
```

`Lax` also permits some top-level cross-site navigations:

```http
Set-Cookie: session=abc123; SameSite=Lax
```

`None` permits cross-site use and requires `Secure`:

```http
Set-Cookie: widget-session=abc123; SameSite=None; Secure
```

Modern browsers commonly treat a missing `SameSite` as `Lax`, with compatibility details. Set it explicitly so the intended behavior is clear.

`SameSite` helps defend against [CSRF](https://flaviocopes.com/csrf/), but it is not the only CSRF control an application may need.

### Partitioned

`Partitioned` requests partitioned storage for a third-party cookie. The browser keeps a separate cookie jar for each top-level site.

It requires `Secure`:

```http
Set-Cookie: widget=compact; SameSite=None; Secure; Partitioned
```

Use this only when building a cross-site embedded feature that needs state.

## Use cookie name prefixes

Cookie prefixes let supporting browsers enforce attribute rules.

`__Host-` is useful for host-bound cookies:

```http
Set-Cookie: __Host-session=abc123; Path=/; Secure; HttpOnly; SameSite=Lax
```

A `__Host-` cookie must use `Secure`, must use `Path=/`, and must not have a `Domain` attribute.

Prefixes add useful checks. They do not replace secure session design.

## Read and write cookies in JavaScript

`document.cookie` exposes non-`HttpOnly` cookies available to the current document:

```js
console.log(document.cookie)
```

The result is one string:

```text
theme=dark; language=en
```

Writing to `document.cookie` adds or updates one cookie. It does not replace every cookie:

```js
document.cookie = 'theme=dark; Path=/; Max-Age=2592000; SameSite=Lax; Secure'
```

Encode values that can contain unsupported characters:

```js
const value = encodeURIComponent('dark mode')
document.cookie = `theme=${value}; Path=/; SameSite=Lax; Secure`
```

`document.cookie` is synchronous and can be awkward to parse. The asynchronous [Cookie Store API](https://developer.mozilla.org/en-US/docs/Web/API/Cookie_Store_API) is an option where your supported browsers provide it.

Authentication cookies should normally be server-set and `HttpOnly`, which means frontend JavaScript should not read them.

## Update or delete a cookie

Update a cookie by setting the same name, domain, and path:

```js
document.cookie = 'theme=light; Path=/; Max-Age=2592000; SameSite=Lax; Secure'
```

Delete it by using the same scope and a non-positive `Max-Age`:

```js
document.cookie = 'theme=; Path=/; Max-Age=0; SameSite=Lax; Secure'
```

If the path or domain does not match the original cookie, you create or delete a different cookie.

## Cookie security rules

For session cookies:

- generate an unpredictable session ID
- keep account data on the server
- use HTTPS and `Secure`
- use `HttpOnly`
- choose an explicit `SameSite` value
- use the smallest practical host and path scope
- rotate the session ID after login and privilege changes
- expire the session on the server, not only in the browser

Do not trust a cookie merely because the browser returned it. Users can modify non-`HttpOnly` cookies, and can send custom HTTP requests.

Signing a value can detect modification. Encryption can hide its contents. Neither one makes authorization checks optional.

See MDN's guide to [using HTTP cookies](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Cookies), the [`Set-Cookie` reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Set-Cookie), and the [`document.cookie` reference](https://developer.mozilla.org/en-US/docs/Web/API/Document/cookie).
