Skip to main content
TF
9 min readArticle

What Are Cookies in Web Dev? A Plain-English Guide

TF
ToolsFuel Team
Web development tools & tips
Developer working on laptop with browser open showing network requests

Photo by Glenn Carstens-Peters on Unsplash

The Cookie Consent Banner That Made Me Read the Spec

A couple of years ago I was asked to add a cookie consent banner to a client's e-commerce site. Seemed simple enough. Then the client's legal team sent over a list of requirements about first-party cookies, third-party tracking, essential vs non-essential, storage duration, and something about SameSite=None.

I realized I'd been using cookies for years without actually understanding what they were doing at the HTTP level. I knew how to set them in JavaScript and how to read them back. I did not know why `SameSite=Lax` was the new default, what `Secure` actually enforced, or why session cookies and persistent cookies were fundamentally different.


So I went and read
RFC 6265 — the actual HTTP State Management Mechanism specification. Genuinely interesting document. A lot of the security edge cases I'd been ignoring turned out to be documented right there in section 8.

Here's what I learned, distilled for anyone who's been in the same boat. No cookie banner politics, just the technical mechanics.

What a Cookie Actually Is

A cookie is a small piece of text data that a server sends to a browser, and the browser sends back with every subsequent request to that domain.

That's it. It's a name-value pair that round-trips between browser and server. The entire mechanism exists because HTTP is stateless — by default, a server has no memory between requests. Without some kind of state mechanism, you'd have to log in on every single page load.


Here's what the HTTP exchange actually looks like:


```http # Server sets a cookie in the response: HTTP/1.1 200 OK Set-Cookie: session_id=abc123; Path=/; HttpOnly; Secure; SameSite=Lax


# Browser sends it back with every request to that domain: GET /dashboard HTTP/1.1 Cookie: session_id=abc123 ```


The browser stores this tiny string and attaches it to every matching request automatically — no JavaScript needed. That's the cookie mechanism in full.


A cookie has a few parts: - **Name**: the key (`session_id`) - **Value**: the data (`abc123`) - **Domain**: which host can receive this cookie (`example.com`) - **Path**: which URL paths the cookie applies to (`/` means all paths) - **Expires/Max-Age**: when the cookie should be deleted - **Flags**: `HttpOnly`, `Secure`, `SameSite` — these control security behavior


The size limit for a single cookie is 4096 bytes (4KB). Browsers also limit the total number of cookies per domain — usually 50 cookies, and the total cookie header sent to a server is typically capped around 4KB as well. So cookies aren't storage. They're for small, frequently-accessed state.

Session Cookies vs Persistent Cookies

Circuit board representing data storage and state management

Photo by Alexandre Debiève on Unsplash

This distinction trips people up because "session cookie" sounds like it's related to server-side sessions (the other thing called "sessions" in web dev). It's not the same thing — it's a coincidence of naming.

**Session cookies** (in the cookie sense) are cookies with no `Expires` or `Max-Age` attribute. The browser keeps them in memory and deletes them when the browser is closed. Close Chrome, session cookies are gone. This is why some sites log you out when you close the browser — they're using session cookies for authentication.


**Persistent cookies** have an `Expires` date or a `Max-Age` value in seconds. The browser stores them on disk and keeps them until that date passes, even if the browser is closed and reopened. "Remember me" checkboxes typically create a persistent cookie with an expiry weeks or months out.


```http # Session cookie (gone when browser closes): Set-Cookie: pref=dark_mode; Path=/


# Persistent cookie (gone after 30 days): Set-Cookie: pref=dark_mode; Path=/; Max-Age=2592000


# Persistent cookie with specific date: Set-Cookie: pref=dark_mode; Path=/; Expires=Wed, 25 Jun 2026 10:00:00 GMT ```


For authentication, the industry-standard pattern is: default to a session cookie (logs out on browser close), but if the user checks "remember me", upgrade to a persistent cookie with an appropriate expiry — 14 to 30 days is common for sensitive applications, longer for low-risk preferences.


One nuance: browser session restore. Chrome and Firefox can restore tabs from a previous session, including in-memory session cookies. So "gone when the browser closes" isn't always true in practice — if the user restores their session, session cookies may persist. Don't rely on session cookies for hard security guarantees.

Cookies vs Sessions — Two Different Things With a Confusing Name

This is the naming collision I mentioned. Let me separate them:

**Cookie** (the HTTP mechanism) — what we've been discussing. A small key-value string stored in the browser and sent with every request.


**Server-side session** — a mechanism for storing arbitrary data on the server, indexed by a session ID. The session ID is typically stored in a cookie, but the session data itself lives on the server (in memory, Redis, a database).


They work together like this:


1. User logs in. Server creates a session object: `{userId: 42, role: 'admin', cart: [...]}` and stores it in Redis with a random ID: `sess:a1b2c3d4`. 2. Server sends the cookie: `Set-Cookie: session=a1b2c3d4; HttpOnly; Secure; SameSite=Lax`. 3. Browser stores `session=a1b2c3d4` as a cookie. 4. On every request, the browser sends `Cookie: session=a1b2c3d4`. 5. Server looks up `sess:a1b2c3d4` in Redis, retrieves the session data, knows who the user is.


The cookie just carries the ID. The actual data — user info, cart contents, permissions — stays on the server. This is important for security: if you store sensitive data in the cookie itself (rather than just an ID), that data is visible to anyone who can read the cookie.


The alternative pattern is JWT authentication — where the token itself contains the user's identity and is verified cryptographically without a server-side lookup. I covered how JWTs work in the post on
what a JWT token actually contains — worth reading alongside this if you're building an auth system and choosing between session cookies and stateless tokens.

Neither approach is universally better. Server-side sessions let you invalidate a user's login instantly (just delete the session from Redis). JWTs can't be revoked until they expire — you'd need a blocklist to replicate that behavior. Sessions require server-side storage. JWTs don't. Your choice depends on your architecture.

The Security Flags That Actually Matter

This is where most developers' knowledge gets fuzzy. Let's go through each flag and what it actually does:

**HttpOnly** — Makes the cookie inaccessible to JavaScript. `document.cookie` won't see it. `document.cookie` can read and write cookies without this flag — meaning an XSS attack that injects JavaScript into your page can steal unprotected authentication cookies. With `HttpOnly`, JavaScript can't touch it, so even a successful XSS attack can't exfiltrate the session token.


Rule of thumb: **authentication cookies should always be HttpOnly.** User preference cookies (theme, language) don't need it since JavaScript needs to read them.


**Secure** — The cookie is only sent over HTTPS connections. If the browser makes an HTTP request (not HTTPS), the cookie is not included. This prevents the cookie from being intercepted on an unencrypted connection — a man-in-the-middle attack on HTTP could grab your session token without `Secure`.


Rule of thumb: **authentication cookies should always be Secure.** If your site doesn't run HTTPS everywhere yet, that's the more urgent problem to fix first.


**SameSite** — Controls whether the cookie is sent with cross-site requests. Three values:


- `SameSite=Strict` — Cookie is only sent for requests that originate from the same site. A user clicking a link from Google to your site? Cookie not sent on that first request. This is very secure but breaks legitimate cross-site navigation to authenticated pages. - `SameSite=Lax` — Cookie is sent for top-level navigation (clicking a link to your site) but not for embedded requests (images, iframes, fetch calls from other sites). This is the default since Chrome 80 and the right setting for most authentication cookies. - `SameSite=None` — Cookie is sent with all cross-site requests. **Requires `Secure` to be set.** This is needed for third-party use cases — embedded widgets, cross-site authentication flows, CDN-hosted content that needs to read a cookie.


The `SameSite` attribute exists to prevent CSRF attacks — where a malicious site tricks a logged-in user's browser into making a request to your server. With `SameSite=Lax`, the attacker's site can't trigger authenticated actions on your behalf.


Here's my standard production cookie header for authentication:


```http Set-Cookie: session=<token>; Path=/; HttpOnly; Secure; SameSite=Lax; Max-Age=86400 ```


HttpOnly keeps JavaScript from stealing it. Secure keeps it off unencrypted connections. SameSite=Lax prevents CSRF. Max-Age=86400 expires it after 24 hours.


The
MDN cookie documentation is excellent for the full reference — much more detailed than I can cover here, including browser-specific behavior quirks and the cookie storage inspector in DevTools.

First-Party vs Third-Party Cookies

The other big category distinction — and the one behind all those consent banners — is first-party vs third-party.

**First-party cookie**: set by the domain the user is visiting. If you're on `example.com` and the server at `example.com` sets a cookie, that's first-party. Your session cookie, your dark mode preference, your shopping cart — all first-party.


**Third-party cookie**: set by a domain different from the one the user is on. If you're on `example.com` and there's a Facebook Like button or a Google Analytics tag, those services can set cookies from their own domains (`facebook.com`, `google.com`). When you visit another site that also has Facebook Like buttons, Facebook can see the same cookie — and knows you visited both sites. That's the cross-site tracking mechanism.


Most modern browsers are eliminating third-party cookies. Safari has blocked them since 2020. Firefox since 2022. Chrome has been rolling out restrictions throughout 2024-2026. The ad industry has been scrambling to find alternatives ever since.


For developers: if you're building something that requires cross-site cookie access (an embeddable widget, a payment iframe, cross-domain SSO), you need `SameSite=None; Secure` and you need to handle the case where browsers block it anyway. Check whether the `Partitioned` attribute (CHIPS — Cookies Having Independent Partitioned State) fits your use case — it allows cross-site cookies that are scoped to the specific top-level site, which satisfies browser restrictions while still enabling the embedded widget pattern.


For regular web apps: first-party cookies, `SameSite=Lax`, and you're largely insulated from the third-party deprecation drama.


If you're debugging cookie behavior in your app, the best tools are your browser's DevTools (Application tab → Cookies in Chrome) and the Network tab (look at Set-Cookie response headers and Cookie request headers). I find myself using the
JSON formatter at ToolsFuel when I'm parsing cookie payloads from an API response — easier to read than raw response text when you're debugging an auth flow.

Frequently Asked Questions

What's the difference between cookies and localStorage?

Cookies are sent automatically with every HTTP request to the matching domain — the browser handles this with no JavaScript needed. localStorage is a client-side only storage that JavaScript can read and write, but it's never automatically sent to the server. Cookies have a 4KB size limit per cookie. localStorage can store around 5-10MB. For authentication, cookies are preferred because they can be HttpOnly (preventing XSS attacks from reading them) and are sent automatically. localStorage tokens are accessible to any JavaScript running on the page. The post on [localStorage vs sessionStorage](/blog/localstorage-vs-sessionstorage-when-to-use-which) covers client-side storage options in more depth.

Why is SameSite=Lax now the default for cookies?

Google changed Chrome's default SameSite behavior to Lax in 2020 (Chrome 80) as a security improvement. Before that change, cookies had no SameSite attribute by default, meaning they were sent with all cross-site requests. This made CSRF (Cross-Site Request Forgery) attacks easier — a malicious site could trigger requests to your server that carried the victim's cookies. Making Lax the default means cookies aren't sent with cross-site subresource requests like images or iframes, blocking the most common CSRF vectors without breaking normal navigation.

Can JavaScript read HttpOnly cookies?

No. That's the entire point of the HttpOnly flag. HttpOnly cookies are invisible to document.cookie and can't be accessed by any JavaScript — not even JavaScript served from the same domain. They're only sent by the browser to the server in the Cookie request header. This makes them resistant to XSS attacks, where an attacker injects malicious JavaScript to steal authentication tokens. If you're storing session tokens or JWTs in cookies, HttpOnly should be mandatory.

What's the difference between Max-Age and Expires for cookies?

Both set when a cookie expires, but they work differently. Expires takes an absolute date in HTTP date format (e.g., 'Thu, 01 Jan 2027 00:00:00 GMT'). Max-Age takes a number of seconds from the current time (e.g., 86400 for 24 hours). Max-Age is preferred because it's relative to the client's current time, whereas Expires can behave unexpectedly if the client's clock is wrong. If both are present, Max-Age takes precedence in modern browsers.

How do I set a cookie in JavaScript?

Use document.cookie: document.cookie = 'theme=dark; Path=/; Max-Age=2592000'. Note that reading document.cookie returns all non-HttpOnly cookies as a semicolon-separated string — you have to parse it yourself to get a specific value. For any security-sensitive cookie (authentication, CSRF tokens), set the cookie server-side in the Set-Cookie response header so you can include HttpOnly and Secure flags, which JavaScript can't set via document.cookie.

Why do cookies get blocked on mobile apps or certain browsers?

Several reasons. Safari's Intelligent Tracking Prevention (ITP) aggressively expires first-party cookies set by JavaScript (not server-side) to 7 days. Browsers in incognito/private mode often don't persist cookies between sessions. Content blockers can block third-party cookie requests. If your app relies on cookies set via JavaScript (document.cookie) rather than server Set-Cookie headers, ITP will bite you on iOS. Always set authentication cookies server-side to avoid these restrictions.

Try ToolsFuel

23+ free online tools for developers, designers, and everyone. No signup required.

Browse All Tools