Auth & Security63 min total · 19 parts
OAuth 2.0 and JWT Explained: What Really Happens When You Click "Sign In"
Part 13 of 19 · ~2 min
Token Storage: Where Should Tokens Live in the Browser
Once Tasklight's backend holds tokens, the question of where the browser side of things keeps whatever credential it needs has real consequences — and Tasklight's five client surfaces don't all answer it the same way, because they don't all have the same options.
| Storage | Readable by JavaScript | Vulnerable to | Sent automatically |
|---|---|---|---|
localStorage / sessionStorage | Yes | XSS — any injected script can read and exfiltrate it | No — must be attached to each request by hand |
httpOnly cookie | No | CSRF — the browser attaches it to anything, including requests a malicious page triggers | Yes, automatically |
Tasklight's web app never puts a token in the browser at all: the access and refresh tokens stay entirely server-side, and Priya's browser only ever holds an httpOnly, Secure, SameSite=Lax session cookie pointing at a server-side session record. No JavaScript on app.tasklight.dev — Tasklight's own or anyone else's, if a dependency were ever compromised — can read that cookie's contents at all.
Tasklight Embed doesn't have that option. It's a script tag a customer drops into their own marketing site, with no backend of its own to keep anything server-side — whatever credential it holds has to live in that page's own browser context, in the open. That's a genuinely forced trade-off, not a shortcut: a widget with no server has nowhere else to put it. What Tasklight Embed does keep is the credential's lifetime short and its scope narrow — a token good only for reading one project's public status badge, refreshed every few minutes rather than held for weeks — because when storage has to be exposed, the next best lever left is making whatever's exposed worth as little as possible for as short a time as possible. This exact trade-off is the direct setup for the attack in a few chapters.
The CLI answers this differently again, because it isn't a browser at all — the table above doesn't even apply to it. When tasklight login finishes, its refresh token goes into the operating system's own credential store: Keychain on macOS, Credential Manager on Windows, the Secret Service API on Linux. That's a third storage location worth naming on its own: not readable by an injected script, because there's no page and no JavaScript runtime for a script to inject into, but also not gone the moment the terminal closes, the way an in-memory-only token would be. It's a real, meaningful improvement over the CLI's next-best option — a token sitting in a plain config file in the developer's home directory, readable by literally anything else running as that user — though it's worth being honest that it isn't magic either: whoever has full access to that machine, or steals it outright, has access to whatever the OS keychain will hand over to a process running as that user. Which is exactly the scenario the next chapter picks up.