Base64 vs. Base64url: Why JWTs Use a Different Alphabet

· 2 min read

Base64 exists to smuggle binary data through systems that only speak text. It maps every 3 bytes of input onto 4 characters from a 64-character alphabet — letters, digits, and two symbols. The problem: the standard alphabet's two symbols are + and /, and its padding character is =. All three are landmines inside a URL.

Where standard Base64 breaks

Put a+b/c9== in a query string and three things go wrong at once:

  • + decodes to a space in form-encoded contexts, so a+b arrives as a b. The data is silently corrupted — no error, just a signature that won't verify.
  • / is the path separator. In a path segment, / splits your token into two segments and probably 404s.
  • = separates keys from values in query strings, confusing parsers that see ?token=a+b/c9==.

You can percent-encode your way around this (%2B, %2F, %3D), but now the token is longer, uglier, and will be mangled by any layer that double-encodes.

The base64url fix

RFC 4648 §5 defines a URL-safe variant with two character swaps and one deletion:

Standard base64url
62nd character + -
63rd character / _
Padding = omitted

Hyphen and underscore are both unreserved URL characters — safe anywhere, never encoded. And the padding turns out to be redundant: the payload length is recoverable from the string length (length % 4 tells you how many bytes the final group held), so base64url simply drops the =.

Standard:  eyJzdWIiOiI0MiJ9+A/w==
base64url: eyJzdWIiOiI0MiJ9-A_w

Why JWTs made base64url famous

A JSON Web Token is three base64url segments joined by dots:

header.payload.signature
eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1c2VyLTQyIn0.dozjgNryP4J3jVmNHl0w5N_XgL0n3I9P

JWTs travel in URLs (password-reset links, magic links), in headers, and in cookies — three contexts where +/= would each cause distinct breakage. The JOSE specifications therefore mandate base64url without padding for every segment. The same reasoning applies anywhere data rides in a URL: OAuth state values, pagination cursors, share tokens, webhook signature parameters.

One thing base64url does not do: encrypt. A JWT payload is encoded, not protected — anyone can decode it. Never put secrets in one; the signature prevents tampering, not reading.

Converting between the two

The conversion is mechanical — swap the two characters, add or strip padding:

Standard → URL-safe: replace + with -, / with _, delete trailing =.

URL-safe → standard: replace - with +, _ with /, append = until the length is a multiple of 4.

The classic bug lives in that second direction: many standard-library decoders reject unpadded input, so a raw JWT segment fed to a strict Base64 decoder throws "invalid length". If a token looks like Base64 but won't decode, missing padding is the first thing to check.

The Base64 URL-safe tool handles all of it — encoding text (full Unicode) to either flavor, decoding either flavor back, and converting between them with auto-detection of which alphabet you pasted.