Base64 vs Base64URL: The Three Characters That Break JWTs and API Tokens
A practical breakdown of the two Base64 variants, real encoding examples showing exactly where they differ, and the four bugs developers hit most often when mixing them up.
Base64 vs Base64URL: The Three Characters That Break JWTs and API Tokens
Standard Base64 and Base64URL look nearly identical. Both turn binary data into ASCII text. Both use the same 62-character core alphabet. The difference is exactly three characters — +, /, and = — and those three characters cause a disproportionate share of authentication bugs, broken API calls, and malformed tokens.
This guide shows the encoding mechanics, a concrete byte-level example, and the four failure modes I've seen most often when developers mix the two variants without realizing it. You can verify every example live with Toolora's Base64 Encoder/Decoder.
What Changes Between Standard Base64 and Base64URL
RFC 4648 (2006) defines both variants in the same document. Section 4 specifies standard Base64; section 5 defines Base64URL. The encoding algorithm is identical. Only the alphabet and padding rules differ:
| Character role | Standard Base64 | Base64URL | |---|---|---| | Index 62 | + | - | | Index 63 | / | _ | | Padding | = required | Usually omitted |
The + → - and / → _ swaps matter because +, /, and = are all reserved or meaningful in URLs and HTML form bodies. In a URL query string, + is parsed as a space by many frameworks (per RFC 1866's application/x-www-form-urlencoded rules). A / in a URL path segment looks like a path separator. An = in a query key-value pair is the assignment character.
JWT tokens use Base64URL because they appear in HTTP Authorization headers and sometimes in URL query parameters directly. A standard Base64 token pasted into a header would silently corrupt the moment a framework decoded the + as a space.
A Real Encoding Comparison
The clearest way to see the difference is with bytes that produce index-62 and index-63 characters. The three bytes 0xFB, 0xEF, 0xFF in binary are:
11111011 11101111 11111111
Grouped into four 6-bit chunks:
111110 | 111110 | 111111 | 111111
62 62 63 63
Standard Base64 maps those four indices to:
Input bytes: 0xFB 0xEF 0xFF
Standard Base64 output: "++//"
Base64URL output: "--__"
No padding here because 3 input bytes produce exactly 4 output characters. Now try 2 bytes — 0xFB, 0xEF:
Input bytes: 0xFB 0xEF
Binary padded to 18 bits: 111110 | 111110 | 111100
Indices: 62, 62, 60
Standard Base64 output: "++8="
Base64URL output: "--8" (padding = omitted)
The = disappears in Base64URL. That one missing character is enough to fail a strict JWT parser that expects unpadded Base64URL.
I tested this exact sequence in Toolora's Base64URL Encoder/Decoder for JWT-safe strings. Pasting ++8= into the decode field with "standard Base64" mode decoded successfully. Pasting the same string into the Base64URL mode returned an error — because + is not part of the Base64URL alphabet at all.
The Four Bugs Developers Hit Most Often
1. Encoding a JWT payload with a standard library and not converting the output. Python's base64.b64encode(), Node's Buffer.from(x).toString('base64'), and most default encoding functions produce standard Base64. If that output goes directly into a JWT segment, any + or / character breaks validators. The fix is a one-line swap: use base64.urlsafe_b64encode() in Python and strip trailing =, or call .replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '') in JavaScript.
2. Re-encoding a Base64URL token for logging or display. Some debugging pipelines re-encode a stored token "for readability." If the re-encoder uses standard Base64, the token looks similar but breaks when consumed by JWT consumers. The - and _ characters survive the round trip visually but don't survive a decode → standard-encode → Base64URL-decode cycle.
3. Percent-encoding a standard Base64 string instead of switching to Base64URL. When developers discover that standard Base64 contains + and /, they sometimes percent-encode the whole value: %2B for +, %2F for /. This works mechanically but doubles the byte length of those characters and produces something JWT libraries reject — they expect Base64URL, not percent-encoded standard Base64. The scope of this problem is wider than it first appears: a 32-byte random secret encodes to a 44-character standard Base64 string, and the binomial probability of that string containing at least one + or / is approximately 1 − (62/64)^44 ≈ 75%. Three-quarters of random 32-byte secrets require escaping on the standard alphabet.
4. Stripping padding from a system that requires it. Base64URL canonically omits = padding; some decoders treat absence of padding as a format error anyway. Meanwhile, stripping = from a standard Base64 string and passing it to a strict RFC 4648 § 4 parser causes "unexpected end of data" failures. The safe rule: preserve the padding convention specified by the receiving system, don't silently strip or add =.
When to Use Which Variant
Use standard Base64 when:
- The encoded value lives in a JSON field, email body, or other context where
+,/, and=have no special meaning. - You are encoding an image for a data URI (
data:image/png;base64,...) — the HTML spec handles standard Base64 in the src context. - A third-party system explicitly cites RFC 4648 § 4 or calls out standard encoding in its documentation.
Use Base64URL when:
- The encoded value is a JWT header, payload, or signature.
- The encoded value appears in a URL path segment or query parameter without a percent-encoding wrapper.
- The encoded value is a cryptographic token distributed via a link — password-reset link, magic sign-in, OAuth
stateparameter. - The spec you are implementing cites RFC 4648 § 5 or uses the phrase "URL-safe Base64."
Quick Sanity Checks Before Shipping
Before you send a Base64-encoded value to production, scan it for:
- Any
+or/in a value that will go into a URL directly → switch to Base64URL. - Any
=at the end of a JWT segment → strip the padding. - Any
-or_in a value destined for a system expecting standard Base64 → convert back using the substitution table above.
If you are unsure which variant a token uses, paste it into both Toolora's Base64 Encoder/Decoder and the Base64URL Encoder/Decoder for JWT-safe strings. A mismatch between the two decoded outputs confirms which alphabet the token actually uses — the correct decoder produces clean bytes; the wrong one produces garbage or an error.
The core rule is short enough to remember: if it belongs in a URL or a JWT, use Base64URL. Everything else stays on the standard variant.
Made by Toolora · Updated 2026-06-09