Skip to main content
CodeOath
← All posts

Auth & Security63 min total · 19 parts

OAuth 2.0 and JWT Explained: What Really Happens When You Click "Sign In"

Part 17 of 19 · ~4 min

Common Attacks and How the Protocol Defends Against Them

There's a real test for whether any of this has actually sunk in: can you point at each design decision above and say, specifically, which attack it exists to shut down? Tasklight isn't hypothetical here either — every one of these has actually happened to it.

CSRF against the OAuth callback (login CSRF). Without protection, an attacker starts their own OAuth flow with Kestrel — using their own account — gets as far as receiving a valid code and state at Tasklight's callback URL, and then, instead of finishing the flow themselves, tricks Priya into loading that exact callback URL in her browser while she's signed in to Tasklight. If Tasklight's callback handler doesn't check that state matches a value it generated and tied to Priya's own browser session, it completes the exchange anyway — quietly linking Priya's Tasklight account to the attacker's Kestrel identity. The next time the attacker logs into their own Kestrel account, they land inside Priya's Tasklight session. The state parameter is exactly what stops this: Tasklight generates it randomly before the redirect, ties it to Priya's own session, and refuses to proceed unless the value coming back matches it precisely. A forged callback the attacker fires doesn't carry the value Priya's own session actually generated, so it gets rejected outright.

XSS stealing a token. This is exactly what token storage was building toward. A customer embeds Tasklight Embed on their marketing site, alongside an unrelated third-party analytics script from a different vendor. That vendor's script later gets compromised in a supply-chain attack, and the injected code starts scanning localStorage across every page it loads on — including the customer's page, where Tasklight Embed's widget token was sitting, readable by any script on that page, including one Tasklight never wrote and the customer never audited. That's the entire cost of the trade-off from the storage chapter: a token that has to be exposed to the page is exposed to anything else running on that page, not just the code that put it there.

CSRF against a cookie-authenticated API is what happens when you take the previous attack and run its logic in reverse. Tasklight Web's session cookie being httpOnly closes off reading it from a script — but it does nothing to stop the browser from attaching that same cookie, automatically, to any request aimed at app.tasklight.dev, no matter which page triggered the request. Get Priya to load a page elsewhere that quietly auto-submits a form to app.tasklight.dev/api/projects/9182/delete, and her browser dutifully includes her real session cookie on the way, no click on anything Tasklight-branded required. SameSite=Lax closes this specific hole by telling the browser to withhold the cookie on a cross-site request in the first place; a server-issued anti-CSRF token on top of that — something a value only Tasklight's own page could have read — closes it more completely still for the endpoints that actually change something.

Authorization code interception and replay doesn't need a new defense at this point, because the earlier chapters already built one. A code that dies in under a minute, that can only ever be spent once, and that — for the CLI — is worthless without a verifier nobody but that CLI process ever generated, leaves an interceptor holding a value with nothing left to do with it.

Algorithm confusion, or signature stripping, is the other repeat customer from earlier in this reference: hand a verifier the power to decide its own algorithm by reading the token's own claim about itself, and you've handed an attacker a way to turn a public key into a forged HMAC secret. Take that power away from the token and give it to the verifier's own configuration, and the trick has nothing left to exploit.

Open redirect via a malicious redirect_uri. Early on, Tasklight's registration with Kestrel allowlisted anything starting with https://app.tasklight.dev/, not the one exact callback path. Tasklight also happens to run a marketing link-shortener on that same domain, at app.tasklight.dev/go/, which will redirect a visitor to nearly any URL it's given. Register that shortener as the redirect_uri, start a login flow, and Kestrel — satisfied that the prefix matched — would hand the authorization code to whatever the attacker pointed the shortener at. Kestrel now requires an exact match against a single pre-registered redirect_uri, not a prefix, which is the standard every serious authorization server enforces for exactly this reason.