A refresh token leaked out of one of our Android TV clients last winter. Not through a server breach — through a rooted device running a sideloaded build that logged every HTTP header to a public Discord channel. The access token in that dump expired in fifteen minutes and nobody cared. The refresh token was valid for thirty days and could mint a fresh access token on demand.

For a service like ViralVidVault, where the trending-video and analytics endpoints are rate-limited per client and every upstream call costs real money in third-party API quota, a single long-lived refresh token in the wrong hands is a slow, quiet leak of both data and budget. Worse, we had no way to tell that the token was being abused, because a stolen refresh token that behaves exactly like a legitimate one is invisible.

This post is about the fix: refresh token rotation with reuse detection. Not the textbook version where you hand-wave "issue a new refresh token each time," but the parts that actually bite you — token families, the race condition when a mobile client retries mid-rotation, storage that survives a device losing power, and how we validate all of it at the edge without a database round-trip. The stack is what we run: PHP 8.4 on LiteSpeed, SQLite in WAL mode, and Cloudflare Workers in front.