Skip to content

fix(oauth): form-urlencode the token refresh (was JSON → /token 400s) - #5

Merged
ZacxDev merged 1 commit into
mainfrom
zach/fix-refresh-formencoded
Jun 19, 2026
Merged

fix(oauth): form-urlencode the token refresh (was JSON → /token 400s)#5
ZacxDev merged 1 commit into
mainfrom
zach/fix-refresh-formencoded

Conversation

@ZacxDev

@ZacxDev ZacxDev commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Refresh() POSTed a JSON body with Content-Type: application/json to
/api/auth/oauth/token, but that endpoint is the @node-oauth/oauth2-server token
handler, which REQUIRES application/x-www-form-urlencoded and rejects JSON with
"content must be application/x-www-form-urlencoded". The custom device-flow
endpoints (/device, /device-token) accept JSON, so LOGIN worked — but every
refresh failed, so OAuth auth silently died after the 1h access-token TTL
(observed live: whoami → "device login failed: ... must be
application/x-www-form-urlencoded").

Fix: add postForm() (x-www-form-urlencoded) and send the refresh grant through
it. Verified live: an expired session refreshes cleanly against prod.

The bug slipped #4's tests because TestRefreshRotatesToken's mock decoded the
request body as JSON (accepting the wrong content-type). Hardened it to assert
Content-Type: application/x-www-form-urlencoded + ParseForm — it now FAILS on a
JSON refresh and PASSES on the form-encoded one.

Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com

Refresh() POSTed a JSON body with Content-Type: application/json to
/api/auth/oauth/token, but that endpoint is the @node-oauth/oauth2-server token
handler, which REQUIRES application/x-www-form-urlencoded and rejects JSON with
"content must be application/x-www-form-urlencoded". The custom device-flow
endpoints (/device, /device-token) accept JSON, so LOGIN worked — but every
refresh failed, so OAuth auth silently died after the 1h access-token TTL
(observed live: `whoami` → "device login failed: ... must be
application/x-www-form-urlencoded").

Fix: add postForm() (x-www-form-urlencoded) and send the refresh grant through
it. Verified live: an expired session refreshes cleanly against prod.

The bug slipped #4's tests because TestRefreshRotatesToken's mock decoded the
request body as JSON (accepting the wrong content-type). Hardened it to assert
Content-Type: application/x-www-form-urlencoded + ParseForm — it now FAILS on a
JSON refresh and PASSES on the form-encoded one.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@ZacxDev
ZacxDev merged commit 9061e23 into main Jun 19, 2026
1 check passed
@ZacxDev
ZacxDev deleted the zach/fix-refresh-formencoded branch June 19, 2026 22:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant