We spent an afternoon convinced the identity provider was broken. Login worked locally. Staging failed with the usual vague error — redirect mismatch, invalid request, pick your vendor wording.
The redirect_uri in our admin console was copied from the ticket. Character for character, it looked identical to what the browser sent. No trailing slash debate. No http vs https surprise.
Then I dumped the authorize URL from the network tab and compared it to what we had registered.
One query value had been encoded twice. Another used + for a space in a place our stack later read as a literal plus. The path wasn't wrong. The encoding layers were fighting each other.
This is the boring class of bug: nothing shows up in a unit test because each function "works" in isolation. The failure only appears when a full URL travels through a form, a framework helper, and a provider that compares strings literally.
What I watch for now:
Before blaming the IdP, decode the authorize URL once in a sane viewer. I want to see each query key and value split apart, not a single opaque percent soup.
Plus vs %20. application/x-www-form-urlencoded treats + as space. Generic URI encoding uses %20. Mix those rules in one redirect chain and you get ghosts.
Nested URLs. Some flows put a return URL inside a query parameter. Encode the inner URL as a component, not as "the whole string again with extra % on the %."
One-line error JSON from the token endpoint still shows up in the same incident. Pretty-printing it once saves ten minutes of guessing whether the problem is OAuth or your own handler.

Browser tabs I keep for these checks — one job per page, local processing, no account for a two-minute look:
https://devcove.dev/en/tools/url-encoder/
https://devcove.dev/en/tools/url-encoder/
https://devcove.dev/en/tools/url-encoder/
I maintain a small toolkit called DevCove for exactly these chores (format, encode, decode in the tab I already have open). curl and server logs still win for reproduction; this is for the moment you're staring at a URL in DevTools and need to know if you're looking at double encoding or a real mismatch.
Not claiming OAuth is easy. Half our fix was process: register the exact bytes the browser will send, document which encoder each service uses, stop "fixing" URLs by encoding until it looks scary.
If you've shipped OAuth lately — where do you catch encoding bugs first? Provider logs, proxy logs, or a scratch pad in the browser?
Top comments (0)