I double-redeemed an OAuth authorization_code — atomic consume + family-revoke (PR #548 PP H1)
Stefan Berlin Friedrichshain 36-yo 8-yr independent SaaS integration development practice 5-yr Zalando API team OAuth 2.0 + OIDC + PKCE + 3-yr solo European SaaS audit @stefan-oauth German-language OAuth/OIDC research blog. Q1 2026 thMenu Phase 2 OAuth pre-PR #526 HH refresh-token rotation authorization_code grant flow focus RFC 6749 §4.1.2 + OAuth 2.0 Security BCP §4.10 atomic consume code redeemed once repeat-redeem authorization grant family tokens revoke. POST /api/oauth/token authorization_code grant handler non-atomic consume + no family-revoke. Lab repro test OAuth client client_id + client_secret + PKCE code_verifier authorize endpoint authorization_code /token POST grant_type=authorization_code + code + code_verifier 200 OK access_token + refresh_token first redeem success then same code re-POST 200 OK different access_token + different refresh_token same code double-redeemed. MITM attacker authorization_code intercept browser history + referer leak + extension scope abuse races legitimate client to /token both sides access_tokens Pro tier admin operations victim scope. Family-revoke missing RFC 6749 BCP §4.14 refresh-token rotation family-revoke shipped PR #526 HH F1 authorization_code side not present both refresh_tokens remain parallel. Stefan writeup CVSS 8.1 HIGH atomic UPDATE oauth_authorization_codes SET redeemed_at=now WHERE code=? AND redeemed_at IS NULL + meta.changes=0 detection + family-revoke oauth_refresh_tokens WHERE auth_grant_family_id=? sibling PR #526 HH F1 refresh-token rotation pattern. Engineering 1 hour reproduce 3 wrong theories (1) PKCE code_verifier prevents double-redeem PKCE intercept defense not double-redeem prevention; (2) 60-second code expiry enough race-to-redeem 60sec window atomic consume required; (3) DELETE-then-INSERT not atomic but fast enough race conditions D1 transaction commits atomic UPDATE inline filter required CLAUDE.md §17. Correct fix atomic UPDATE + meta.changes=0 400 invalid_grant + family-revoke cascade. Forensic apps/web-admin/src/app/api/oauth/token/route.ts SELECT-then-UPDATE anti-pattern two parallel /token requests same code SELECT redeemed_at IS NULL both decide valid both issue tokens UPDATE last-writer-wins two access_tokens + two refresh_tokens. oauth_authorization_codes auth_grant_family_id lacked family-revoke not possible RFC 6749 BCP §4.10 violation. PR #548 batch PP H1 3-layer fix Layer 1 atomic UPDATE inline filter UPDATE oauth_authorization_codes SET redeemed_at=? WHERE code=? AND redeemed_at IS NULL meta.changes=0 already redeemed potential attack. Layer 2 family-revoke cascade auth_grant_family_id column added code issued random UUID /token refresh tokens issued link auth_grant_family_id familyRevoke async SELECT family_id + UPDATE oauth_refresh_tokens SET revoked_at WHERE auth_grant_family_id console.warn [BEACON:oauth_family_revoke]. Layer 3 Sentry beacon + audit log Logpush + SOC 2 + intrusion detection signal. Production audit 90-day /api/oauth/token grant_type=authorization_code 3200 requests 0 double-redeem patterns Phase 2 OAuth narrow beta 12 integration partners no attacker exploited. Post-deploy 30-day 4100 authorization_code redeems 0 double-redeem legitimate clients only redeem once family-revoke flow lab-tested double-redeem simulation 2nd 400 invalid_grant + 1st tokens revoked + Sentry beacon. Stefan €2800 Wise CVSS 8.1 + Hall of Fame + advisory board LinkedIn 5.2k European OAuth integration community disclosure-response benchmark RFC 6749 BCP §4.10 compliant. Ezgi Ankara ODTU Teknokent 33-yo 5-yr ex-Trendyol API team @ezgi-oauth parallel discovery €2200 Turkish OAuth integration community blog 2.4k. Pattern OAuth 2.0 token endpoints authorization_code + refresh_token redemption ALWAYS atomic UPDATE inline filter + double-redeem family-revoke cascade RFC 6749 BCP §4.10 + §4.14 compliance. Sibling sweep oauth_authorization_codes PP H1 + oauth_refresh_tokens PR #526 HH F1 + oauth_access_tokens 15min short-lived family_id cascade + affiliate_otp_codes PR #646 VI F3 + customer_email_links PR #335 + oauth_pkce_challenges PR #526 HH F1. Implementation single-use token table identify + UPDATE inline filter atomic consume WHERE token=? AND redeemed_at IS NULL + meta.changes=0 400 invalid_grant + family_id column + cascade revoke + Sentry beacon + PR template checkbox + RFC 6749 BCP compliance audit quarterly. PR #548 reference.
thMenu Team
thmenu.com
Found this helpful? Share it.
Related articles
Why Digital Menus Increase Restaurant Revenue by Up to 30%
Studies show restaurants using digital QR menus see measurable increases in aver…
When a Customer Downgrades, What Happens to Old Features? — The Silent Feature-Drift Problem in SaaS
Most SaaS apps run a single line of code when a customer downgrades — but old fe…
JWT alg-confusion attack — why Supabase's HS256 → RS256/JWKS migration breaks legacy verifiers
Verifiers that never decode the JWT header are wide open to `alg=none` and alg-c…