Why JWT uses Base64URL
August 9, 2026 · 14 min read
JSON Web Tokens are three dot-separated segments. The header and payload are JSON objects serialized to UTF-8, then encoded with Base64URL - not standard Base64. That choice is deliberate: tokens must travel in Authorization headers, query strings, and form posts without character escaping breaking verification.
Understanding why JOSE picked Base64URL helps you debug libraries that “almost work” and explains why copying a payload into a standard Base64 decoder often fails.
What the JOSE spec says
RFC 7519 defines JWT; RFC 7515 (JWS) defines how signing input is built. The signing input is the ASCII concatenation BASE64URL(UTF8(header)) + '.' + BASE64URL(UTF8(payload)). Base64URL encoding follows RFC 4648 with the URL-safe alphabet and optional omission of padding.
The signature segment is also Base64URL-encoded binary output from HMAC or asymmetric algorithms - not JSON, not hex.
URL and header safety
Standard Base64’s + and / collide with URL parsing and filename rules. OAuth redirects and SPA hash routes often carry tokens in the fragment or query. Base64URL’s - and _ reduce the need for percent-encoding and prevent subtle corruption when intermediaries rewrite URLs.
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 ← Base64URL header
.
eyJzdWIiOiIxMjM0NTY3ODkwIn0 ← Base64URL payload
.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c ← signature
Why padding is omitted
Padding = characters are awkward in URLs (some stacks strip them) and add length to every token. JOSE decoders calculate missing bits from len % 4. Implementations must accept unpadded input; encoders should omit = unless a downstream system incorrectly requires it.
If you manually pad before verification, pad consistently - mixed padding policies between issuer and verifier cause rare interoperability bugs.
Impact on signatures
The signature is computed over the encoded header and payload strings as they appear in the token, not over decoded JSON. Re-encoding JSON with different whitespace changes the signature input even if claims are semantically identical. Never pretty-print segments before signing.
// Signing input (literal bytes, no pretty JSON)
const data = new TextEncoder().encode(`${b64Header}.${b64Payload}`);
// HMAC or sign data, then base64url-encode the signature bytes
Common mistakes
Using Buffer.from(s, 'base64') on JWT segments without URL-safe translation fails. Piping segments through online “standard Base64” decoders yields mojibake. Assuming decode equals trust skips signature verification - the encoding is only for transport, not security.
When building learning tools, label segments “Base64URL” explicitly so developers do not conflate JWT with MIME Base64.
FAQ
- Could JWT have used standard Base64?
- Technically yes, but tokens would need more escaping in URLs and headers. The ecosystem standardized on Base64URL for interoperability.
- Is the JWT payload encrypted?
- No. Base64URL is encoding only. Use JWE for encrypted tokens.
- Why do I see - and _ in my token?
- Those are Base64URL characters (values 62 and 63), not mistakes.
- Does changing alg in the header affect encoding?
- Encoding format stays Base64URL; content of JSON changes. Signature must be recomputed over the new header bytes.
Related: Base64URL vs Base64