A Stolen Token Should Be Worth Nothing
Last night I pulled a refresh token out of my own browser's storage — the kind of thing an infostealer does in under a second — and tried to use it against a test server I'd wired up with Google's new OAuth option turned on. It failed. Not because the token had expired. It still had weeks left on it. It failed because, on its own, it was no longer enough to prove anything.
A refresh token, as every backend engineer learns the hard way, is a bearer token: whoever holds it is you, as far as the server is concerned, the way a subway ticket doesn't ask whose hand is holding it. Steal one out of a browser's local storage, which malware does constantly and casually, and you don't need the password, the device, or the person. You just need the string.
What Google shipped this week is DPoP — Demonstrating Proof-of-Possession — and the idea is almost embarrassingly simple once you see it: the moment my app first gets a token, it also generates a private key that never leaves the device, and every single request after that has to come signed by that key. The token stops being a ticket and starts being half of a lock. The malware can lift the string all day; without the key, the string opens nothing.
I'd already made peace with short-lived tokens — expire fast, re-issue often, so a theft has a small window. DPoP is a different animal entirely, and a better one: it doesn't shrink the window, it removes the thing a thief actually wanted. I generated my test key with a single call, bound it to the token request, and then spent twenty minutes trying to replay the stolen token from a second machine, the way an attacker would, just to watch it fail the signature check every time.
Somewhere a stolen token is sitting in a log file on a server I'll never see, as inert as a photocopy of a key that was never cut. I left the test running overnight, replaying that same dead token every few minutes, and this morning the server was still saying, patiently, no — not because it forgot who I was, but because it never believed the token alone was me in the first place.
On the wire this week
- Announcing DPoP Support for Google OAuth: Securing Refresh Tokens Google Developers Blog
Yuki Halvorsen was born in Sapporo to a Norwegian father and a Japanese mother, wrote her first program on a school computer that took ten seconds to draw a circle, and spent eight years as an engineer in Stockholm before Tokyo pulled her back. She still ships something small every night, believes the only honest review of a model is the thing you built with it, and writes Accept All from a desk in Nakameguro where the terminal is always open.