The Plain Text Username Mistake
If a cookie holds a raw username and the server trusts it unconditionally, becoming another user is not hacking. It's editing a text field in DevTools.
The last post covered how far a cookie's reach can extend. This post is about something simpler and, honestly, more embarrassing: what happens when the value inside the cookie is just a username, sitting there in plain text, and the server trusts it completely.
A Small Note-Taking App, and a Bad Assumption
Picture a small note-taking app. It's an Express server, Node underneath, a SQLite database with a couple of demo accounts already seeded in.
One of those accounts belongs to a user named Peter. Peter logs in, and the server responds with a cookie:
Set-Cookie: user=peterFrom here on, every request Peter's browser makes carries that cookie back. The server looks at user=peter, matches it against the database, and decides "this request is Peter" without asking for anything else. No password check on later requests, no signature, no proof of any kind beyond the raw string matching a name in a table.
That's the entire authentication model. It works, right up until someone realizes what it actually is.
Becoming Someone Else Takes Ten Seconds
Open DevTools, go to the Application tab, find the cookie, and there it is: user=peter, plain and editable.
Change it to user=denver, refresh the page, and the app now believes you are Denver. No password guessed, no exploit written, no vulnerability scanner involved. Just a text field, edited by hand, in a panel every browser ships with by default.
ExpandA server trusting user=neal completely, an attacker editing that value in DevTools, and the server now treating them as Neal with no way to tell the difference.
This is the crucial mistake: storing raw identity in a cookie the server trusts unconditionally. Everything downstream of that decision, every note Denver ever wrote, every setting on his account, is now reachable by anyone willing to type six characters into a browser panel.
Obfuscating the Value Does Not Fix This
The instinct here is usually "encrypt it, then." Base64-encode the username, maybe run it through a simple cipher, and the field in DevTools stops being readable at a glance.
That's treating the symptom, not the disease. The problem was never that an attacker could read the cookie. The problem is that the server accepts whatever value shows up and treats it as proven identity.
Encrypt user=denver into something unreadable, and an attacker who can't decode it still has options: capture a real session cookie belonging to someone else and replay it, or if they can predict or intercept a valid encrypted value some other way, paste it in and get the same result as before. The architecture is still trusting client-supplied identity unconditionally. You've only made the exploit slightly less obvious to a casual observer, not actually harder to pull off.
Every layer of obfuscation added on top of a broken trust model buys a little time and nothing more. Eventually, whether through more sophistication or plain bad luck, the same failure shows up again with extra steps.
Why This Keeps Happening
Nobody sets out to write user=peter and ship it. It happens because the naive version genuinely does work, in the demo, on your own machine, with only one browser tab open.
Login flows built this way pass every manual test a developer runs. Log in as Peter, see Peter's notes, log out, log in as someone else, see their notes instead. Everything behaves exactly as expected, because nobody on the happy path ever opens DevTools and edits a cookie by hand.
The gap only shows up when someone actively looks for it, which is precisely why it survives code review so often. A reviewer skimming for "does the login work" will approve this every time, because it does work, for the one scenario anyone thought to check.
What Actually Needs to Change
The fix isn't a cleverer encoding. It's removing the server's willingness to trust a value it never verified.
That takes two separate ideas, and this series covers them in order:
- Prove the cookie hasn't been tampered with, even if its value stays readable
- Stop putting real identity in the cookie at all, and hand out something meaningless to everyone except the server
The first is signing. The second is the idea of a session, and it changes more than it looks like at first.
What This Sets Up
A raw, trusted identity value is the root problem here, not the readability of that value. Fixing it means the server has to start asking for proof, not just accepting whatever text arrives.
That proof starts with locking the cookie away from JavaScript entirely, which turns out to matter for a completely different reason than the one covered in this post.
The Essentials
- Storing raw identity in a cookie the server trusts unconditionally means anyone can become anyone, just by editing the value.
- DevTools makes this trivially accessible. No exploit code, no scanner, just the Application tab every browser ships with.
- Encrypting or obfuscating the value does not fix the root problem. The server is still trusting client-supplied identity without proof.
- The real fix has two parts: prove the value wasn't tampered with, and stop storing real identity in the cookie at all.
- This mistake rarely looks like a mistake while it's being written. It looks like the simplest possible way to remember who's logged in.
Next up: locking cookies away from JavaScript with HttpOnly, and why sessions exist as a separate idea from "remembering who someone is."
Further Reading and Watching
- Cookie Stealing: Computerphile's Dr. Mike Pound demonstrates what an attacker can actually do once they get hold of a cookie's value.
- OWASP Session Management Cheat Sheet: practical guidance on session ID properties and cookie security attributes that this exact mistake ignores.
Keep reading