Cookies Gone Wrong: Plain Text, Tampering & the Mistakes That Haunt Us
๐ช Cookies Overview โ How Browsers Remember Who You Are
HTTP Is Stateless โ Cookies Are the Cheat Code
HTTP has no built-in concept of "you are logged in." Every request is, in isolation, anonymous. Cookies are the agreed-upon workaround: the server tells the browser who it is via a Set-Cookie response header, and the browser dutifully echoes that value back on every subsequent request.
That's it. That's the whole mechanism. The server says "you are this person," the browser remembers, and from that point on the server trusts that claim.
"If somebody else can say 'I am Bobby Tables' โ as far as the server is concerned, they are."
This is the foundational threat model for everything that follows in this course. Authentication on the web is really just possession of a token. Whoever holds the cookie is the user, full stop.
๐ Cookies Aren't Just for Auth
It's worth being precise about what cookies actually do, because "cookies = login" is only part of the picture:
- Authenticated sessions โ the one we care most about from a security perspective. This cookie ties you to your account, your permissions, your payment info.
- Unauthenticated tracking โ advertisers don't need to know who you are. They just need a persistent identifier to know you looked at guitars three times this week. That's a cookie too.
- Shopping cart persistence โ you can add items as an anonymous user, log in at checkout, and your cart survives. Also cookies.
The security risk scales with what's attached to the session. An anonymous shopping cart cookie? Low stakes. A cookie tied to an authenticated session with a saved credit card? That's the target.
๐ A Brief, Painful History (That Explains Why Things Are Weird)
This is context that actually matters for understanding why some cookie behavior feels illogical.
| Year | Event |
|---|---|
| 1994 | Netscape ships cookies with zero spec โ spent a few days on it |
| 1997 | First attempt to standardize (Cookie2) โ incompatible, never adopted |
| 2000 | Another standardization attempt |
| 2011 | Finally: an actual spec with real cross-browser rules |
That means cookies existed for 17 years without a proper specification. Longer unspecced than specced. A lot of the weird behavior, the sharp edges, the security footguns โ they're not bugs. They're fossils from the grunge era of the web that we can't remove without breaking everything.
"Some of the worst parts of securing your web application are choices people made during the grunge era. And now we live with them."
๐คจ The document.cookie Gotcha
Here's a small but representative example of how cookies are weird in ways that bite you if you assume normal JavaScript behavior.
// You might think this replaces the cookie entirely โ it doesn't
document.cookie = "username=alice";
document.cookie = "username=bob"; // doesn't overwrite โ appends a new cookieIt actually appends a new cookie rather than overwriting. The browser stores cookies as one big semicolon-delimited string and parses it out into a table โ it's not a key-value object behaving the way you'd expect.
This matters because the gaps between what you assume and what's actually happening are exactly where attackers operate.
๐ Viewing & Deleting Cookies in DevTools
During the course exercises, you'll want to inspect and clean up cookies regularly. In Chrome:
- Open DevTools โ Application tab
- Expand Cookies in the left sidebar
- Select your localhost origin to see all cookies as a parsed table
You can also delete individual cookies here โ useful when you're testing exploits and need a clean slate between attempts.
โ๏ธ Cookie Attributes โ The Settings That Can Save or Sink You
Cookies Are More Than Key-Value Pairs
Think of a cookie as a key-value store with a set of extra metadata properties attached โ agreements between the server and browser about how that cookie should behave. As of 2011 these are standardized, but they still carry a lot of legacy footguns.
โณ Expiration: When Should the Browser Forget You?
You have two options for controlling cookie lifetime:
Expiresโ set an explicit date (e.g.Expires=Thu, 31 Dec 2025 23:59:59 GMT)Max-Ageโ set a duration in seconds (e.g.Max-Age=86400for 24 hours)
If you set neither, the cookie lives for the duration of the session โ roughly until the browser is closed. That's a reasonable heuristic, though not universally true across all browsers and configurations.
A fun piece of trivia Steve dropped: Google once set a cookie expiring in 2038 โ the year 32-bit integers overflow. Unsurprisingly, that creeped people out.
General guidance on expiry timing:
| Cookie Type | Suggested Lifetime |
|---|---|
| Authenticated session | Short โ hours or days |
| User preferences (dark mode, etc.) | Longer โ weeks or months |
| Tracking/advertising | As long as legally allowed |
๐๏ธ How to "Delete" a Cookie (You Can't, Really)
This is one of those things that sounds simple until you realize it isn't. You cannot directly delete a cookie. What you actually do is set its expiration date to the past โ the browser sees it's already expired and clears it.
// "Deleting" a cookie by backdating its expiry
document.cookie = "username=; expires=Thu, 01 Jan 1970 00:00:00 GMT";The browser receives this, notices it expired in 1970, and removes it. It's a convention, not a real delete operation.
โ ๏ธ Attributes You Probably Shouldn't Touch Without a Good Reason
Path
Lets you scope a cookie to a specific subdirectory โ e.g. only send this cookie for requests under /admin. Sounds useful, but there are iframe-based exploits that can make this scoping unreliable. Steve flags it as something with unintended consequences.
Domain โ The Sneaky One
This one deserves careful attention. By default, a cookie is scoped to the exact domain that set it. But if you explicitly set the Domain attribute with a leading dot, it applies to that domain and all its subdomains.
Set-Cookie: session=abc123; Domain=.yourcompany.comThat could be intentional and fine โ say you want login.yourcompany.com and dashboard.yourcompany.com to share a session cookie.
But here's where it goes wrong: what if your app is hosted on a shared platform like github.io or vercel.app? Setting Domain=.github.io means your cookie is scoped to every other site also hosted on github.io. You just handed your cookie to every stranger on that subdomain.
"You didn't even realize it, but you just gave anyone else on that subdomain โ or anyone who gets one of those subdomains โ access."
This is the kind of thing a server-side framework might set silently in the background. You don't configure it, you don't notice it, and suddenly you have a much bigger attack surface than you intended. Always verify what your framework is actually sending in the Set-Cookie header.
Next up: Plain Text Passwords โ why storing user data directly in cookies is a disaster, and how Cookie Parser helps (but doesn't fix everything).
๐ Plain Text Passwords โ The Most Obvious Mistake (That Still Happens)
Don't Roll Your Own Anything
Before getting to the main vulnerability, Steve establishes a principle that will echo throughout the entire course:
"Don't write your own database, don't roll your own crypto. When it comes to sanitizers or anything like that โ grab a library."
The specific example here is cookie-parser โ a well-named Express middleware that does exactly one thing: takes the raw Cookie request header (a single semicolon-delimited string) and parses it into a proper key-value object so you're not doing string manipulation by hand.
// Without cookie-parser, you're manually splitting this:
// "username=bobbytables; theme=dark; session=abc123"
// With cookie-parser as middleware:
app.use(cookieParser());
// Now req.cookies.username === "bobbytables" โ clean and safeWhy does this matter from a security perspective? Because hand-rolling parsing logic is where subtle bugs live. The library has had many eyes on it across the industry. Your one-off implementation hasn't.
One caveat Steve flags: do your research on the library itself. The csurf package โ a CSRF protection module that was actually built into Express โ was deprecated because it was riddled with security holes. A library isn't automatically safe just because it exists. Check that it's actively maintained and not on a known deprecation list.
๐จ The Plain Text Cookie Problem
Here's the core vulnerability demonstrated in the cookie-jar example app. After logging in, the server sets a cookie that looks something like this:
Set-Cookie: username=bobbytablesThat's it. The actual username, in plain text, stored directly in the cookie.
This is already broken in the most fundamental way possible. Anyone who can read or modify that cookie can simply change the value:
# Attacker opens DevTools, edits the cookie value:
username=adminNow the next request goes out with username=admin. The server looks up that username, finds the admin account, and serves the page as if that user is authenticated. No password required. No token. Just... change the string.
"Anyone can go ahead and decide โ 'you know what, I'm going to be someone else.' And now when the browser sends that request, we look for that username directly."
๐ง Why Obfuscation Isn't the Answer
You might think: okay, just encrypt the username value in the cookie. That's harder to guess.
Steve addresses this directly. Encryption, obfuscation, encoding โ these add layers of sophistication to the attack, but they don't change the fundamental shape of the problem. If someone can intercept, steal, or forge the cookie value at any level of sophistication, you end up in the same place: an attacker impersonating a legitimate user.
The real fix isn't to hide the username better โ it's to stop putting the username in the cookie at all. That's what sessions are for, which is exactly where the course goes next.
Next up: Sessions & HttpOnly โ how sessions abstract identity away from the cookie, and why HttpOnly is one of the most important flags you can set.
Next up: Part 2 โ Sessions, HttpOnly, signing cookies, and the Same Origin Policy.