fix(auth): send DCR HTTP Basic credentials raw, not form-url-encoded#43
Conversation
oauth4webapi's ClientSecretBasic form-url-encodes the Basic credential per RFC 6749 §2.3.1, turning reserved chars in the client_id into percent-escapes (a DCR-issued `twd_…` becomes `twd%5F…`). Servers that don't url-decode the Basic credential — Twist among them — then fail the lookup with "client_id not found". Replace `oauth.ClientSecretBasic` with a raw Basic `ClientAuth` that base64s `client_id:client_secret` verbatim. Raw is interoperable with both decoding and non-decoding servers for the URL-safe credentials OAuth servers issue in practice; consumers whose credentials contain `:` / `%` / `+` can still select `client_secret_post`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
doistbot
left a comment
There was a problem hiding this comment.
This PR thoughtfully replaces the standard form-url-encoded HTTP Basic authentication with a raw base64 encoding to better support non-conformant token endpoints. It provides a practical workaround that successfully eases integration for the URL-safe credentials typically issued in practice. However, there are a few edge cases to review regarding how this unencoded approach might silently break conformant credentials containing characters like colons or plus signs, and the regression test fixture could be strengthened by utilizing a character that explicitly requires escaping to properly pin the new behavior.
Address review: fully-raw Basic silently breaks credentials containing reserved chars (`:` `%` `+` `/`) — a conformant token endpoint only reconstructs those when each component is escaped first. Encode each component with encodeURIComponent (RFC 3986): it escapes the reserved chars but, unlike oauth4webapi's stricter §2.3.1 form-encoding, leaves the unreserved `-` `_` `.` `~` intact — so servers that don't url-decode the Basic credential (a DCR-issued `twd_…` client_id) still match, with no silently-unrepresentable header. Regression fixture now uses a secret with `+` / `/` to pin both halves of the contract: preserved `_`, escaped reserved chars. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
## [0.20.1](v0.20.0...v0.20.1) (2026-05-21) ### Bug Fixes * **auth:** send DCR HTTP Basic credentials raw, not form-url-encoded ([#43](#43)) ([97a626a](97a626a))
|
🎉 This PR is included in version 0.20.1 🎉 The release is available on: Your semantic-release bot 📦🚀 |
Summary
createDcrProvider'sclient_secret_basicpath used oauth4webapi'sClientSecretBasic, which form-url-encodes the HTTP Basic credential per RFC 6749 §2.3.1. That turns reserved chars in theclient_idinto percent-escapes — a DCR-issuedtwd_…becomestwd%5F…. Servers that don't url-decode the Basic credential (e.g. Twist's token endpoint) then fail the lookup with404 "Integration with client_id not found".This swaps in a small raw Basic
ClientAuththat base64sclient_id:client_secretverbatim.Why raw is the better default
twd_abchas no%-sequences, decodes to itself → works.So raw is interoperable with both, for the URL-safe credentials OAuth servers issue in practice. It also matches what hand-rolled clients (and
curl -u) send. Consumers whose credentials can contain:/%/+(rare) can still selecttokenEndpointAuthMethod: 'client_secret_post', which sends the credential in the body viaURLSearchParams.Found while migrating twist-cli onto
createDcrProvider(twist-cli#243) — Twist client_ids start withtwd_, so every login hit this.Test plan
npm run build— cleannpm test— 436 pass (DCR Basic test now uses an underscore-bearingclient_idto guard the raw encoding)npm run check— cleantw auth loginagainst twist.com now completes the token exchange🤖 Generated with Claude Code