Skip to main content

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.

Published By Lei Li
#encoding #jwt #developer #base64 #debugging

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 state parameter.
  • 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