OAuth 2.0's redirect_uri parameter has stricter encoding requirements than almost any other URL component — because it appears inside another URL, inside an HTML page, potentially inside a JavaScript string, potentially inside a shell command — and each layer applies its own encoding rules, creating a sequence of encoding and decoding operations where a single mismatch produces an error that's extremely difficult to diagnose from the error message alone
URL encoding (percent-encoding) is one of those topics that seems trivially simple until you encounter a real-world bug. The percent sign followed by two hex digits looks obvious in isolation. In production, encoding bugs hide inside configuration strings, appear in log files that have been double-encoded, corrupt data passed between systems that encode differently, and produce error messages that don't mention encoding at all.
The percent-encoding specification and its reserved vs unreserved categories
RFC 3986 (the authoritative URI specification) divides characters into three categories:
Unreserved characters: A-Z, a-z, 0-9, -, _, ., ~ — these must NOT be percent-encoded. Encoding them is technically valid (the spec permits encoding any character) but is unnecessary and can cause comparison failures if one system encodes A as %41 and another leaves it as A.
Reserved characters: :, /, ?, #, [, ], @, !, $, &, ', (, ), *, +, ,, ;, = — these have syntactic significance in URIs. They should be encoded when they appear as literal data values, not as URI structure characters.
Other characters: all characters not in the above categories, including spaces (encoded as %20), most Unicode characters (encoded as UTF-8 bytes, each byte separately percent-encoded), and control characters.
The common confusion: + for space encoding. HTML form encoding (application/x-www-form-urlencoded) uses + to represent spaces. URI encoding (RFC 3986) uses %20. When URL-encoded form data is used in a URI context, + signs may not be decoded to spaces, creating bugs where form submissions containing spaces produce + signs in the URL that appear literally instead of as spaces.
When URL encoding applies in different contexts
The key principle: different parts of a URL have different encoding contexts, and the same character may need different treatment in each.
Path component: /api/resource/value — path segments are delimited by /. A / appearing as literal data within a path segment must be encoded as %2F. Other reserved characters may or may not need encoding depending on their meaning in context.
Query string: ?key=value&other=value2 — key-value pairs are delimited by & and =. These must be encoded if they appear within key or value strings. Spaces become %20 (URI encoding) or + (form encoding).
Fragment: #section-name — the fragment is never sent to the server; it's client-side only.
A URL within a URL (as a query parameter value): the most complex case:
https://auth.example.com/authorize?
response_type=code
&client_id=abc123
&redirect_uri=https%3A%2F%2Fapp.example.com%2Fcallback%3Fstate%3D123
The redirect_uri value must have its own URL-special characters (://, /, ?, =) percent-encoded, because they appear inside a query parameter value where they'd otherwise be interpreted as URL structure rather than literal data.
OAuth redirect_uri: the most common URL encoding bug in production
The OAuth 2.0 flow requires the redirect_uri parameter in the authorization request to exactly match the registered redirect URI for the client. "Exactly match" means character-for-character identical after decoding — or in strict implementations, before decoding:
The common failure scenario:
- Developer registers redirect URI:
https://app.example.com/callback - Application constructs the authorization URL using a URL builder that encodes the redirect_uri:
redirect_uri=https%3A%2F%2Fapp.example.com%2Fcallback - Authorization server receives the request, decodes
redirect_uritohttps://app.example.com/callback→ matches registered URI → OK - Developer adds a state parameter: now redirect URI becomes
https://app.example.com/callback?state=xyz - Encoded:
redirect_uri=https%3A%2F%2Fapp.example.com%2Fcallback%3Fstate%3Dxyz - But the registered URI is
https://app.example.com/callback— no state parameter - After decoding, the redirect_uri has
?state=xyzappended → doesn't match registered URI → OAuth error: redirect_uri mismatch
The correct approach: the state parameter should be a top-level OAuth parameter, not appended to the redirect_uri. The redirect_uri should remain constant and registered.
Double encoding: when encoded content gets encoded again
Double encoding occurs when percent-encoded content is encoded a second time:
Original: Hello World
First encoding: Hello%20World
Second encoding (treating % as a character to encode): Hello%2520World
The %25 signature: %25 in a URL is the percent-encoded percent sign. Seeing %25 in a URL often indicates double encoding — the original content had a %20 (or other percent sequence) that was then encoded again, turning % into %25.
How double encoding happens in practice:
- A URL is stored in a database already encoded
- Code retrieves it and encodes it again before use
- Template engines that encode on output when the input is already encoded
- Copying a URL from an already-encoded display (browser address bar) and pasting it into a system that encodes it again
Punycode and international domain names in URLs
International domain names (IDN) — domains containing non-ASCII characters — are represented in DNS as Punycode, which uses only ASCII characters:
münchen.de → xn--mnchen-3ya.de (Punycode representation)
The URL context: a URL containing an IDN domain has the Punycode version in the actual URL used for connections, while browsers display the Unicode version in the address bar:
- What the browser displays:
https://münchen.de/page - What's actually in the HTTP request:
https://xn--mnchen-3ya.de/page
The homograph attack: visual similarity between Unicode characters from different scripts enables "lookalike" domains. аpple.com (with a Cyrillic 'а') looks identical to apple.com (with a Latin 'a') but is a completely different domain. Browsers now display the Punycode for domains using mixed scripts to prevent this — xn--pple-43d.com is unambiguous.
How to use the URL Encoder/Decoder on sadiqbd.com
- For OAuth debugging: decode the complete authorization URL from your logs or browser address bar to see the human-readable version of each parameter, identifying any encoding issues in the redirect_uri or state parameter
- For double encoding detection: if a URL behaves unexpectedly, decode it twice — if the second decode produces a different result from the first, the URL was double-encoded
- For query parameter encoding validation: encode and decode your API query parameter values to verify that special characters (spaces,
&,=,+) in values are correctly escaped, particularly when values themselves contain URL-reserved characters
Frequently Asked Questions
What's the difference between encodeURI() and encodeURIComponent() in JavaScript, and when should I use each?
encodeURI() encodes a complete URL, leaving URL-structure characters intact; encodeURIComponent() encodes a string for use as a URL component value, encoding all characters that have special meaning in URL structure. encodeURI('https://example.com/path?key=value with spaces') → https://example.com/path?key=value%20with%20spaces — the ://, /, ?, and = are left unencoded because they're valid URL structure characters. encodeURIComponent('value with spaces & special=chars') → value%20with%20spaces%20%26%20special%3Dchars — the & and = are encoded because they'd be interpreted as query string delimiters if left raw in a parameter value. Use encodeURI for complete URLs; use encodeURIComponent for individual parameter values that you then assemble into a URL.
Is the URL Encoder/Decoder free? Yes — completely free, no sign-up required.
Try the URL Encoder/Decoder free at sadiqbd.com — percent-encode and decode URLs and URL components instantly.