Logo
Back to Blog
Development August 8, 2026 7 min read

Base64URL vs Base64: Two Characters That Break Everything

O

OmniWebKit Team

Developer Tooling

Share:
Article Cover Image

Your token works in Postman and fails in the browser. The decoder says invalid input on a string that looks perfectly fine. Two characters are the difference, and almost nothing tells you which variant you are holding.

Base64URL vs Base64: What Actually Changes

Two characters swap, and the padding usually disappears. That is the entire difference.

ValueStandard Base64Base64URL
62+-
63/_
Padding=usually omitted

The other 62 characters are identical. The maths is identical. Decoded output is byte-for-byte the same.

Both variants are defined in RFC 4648, standard Base64 in section 4 and the URL-safe form in section 5.

Why URL Safe Base64 Had to Exist

Because plus and slash both mean something specific inside a URL.

A slash separates path segments. Put one in a Base64 string inside a URL path and you have accidentally created a new directory level.

A plus is worse, because it is ambiguous. In a query string it historically means a space, so a+b may arrive at your server as a b. Your string is now corrupt and nothing errored.

Equals signs are the third problem. They separate keys from values, so they get percent-encoded into %3D — three characters where you had one.

In our experience the plus-becomes-space bug is the nastiest of the three, because it fails silently and only on strings unlucky enough to contain a plus.

JWT Base64URL: Where You Meet It Most

Every JSON Web Token is three base64url segments joined by dots.

eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjMifQ.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk

Header, payload, signature. Notice the dash and underscore in that third segment — a standard decoder will reject them.

Pasting the whole token into a decoder fails too, because the dots are not valid Base64 characters. Split on the dots first, then decode each part.

Our JWT decoder handles the splitting and the variant automatically, which is faster than doing it by hand.

One caveat that costs people real time: never re-encode a segment and expect the signature to still verify. The signature covers the exact original text, so adding padding back invalidates it.

Base64 Padding Removal and How to Put It Back

Pad the string with equals signs until its length divides by four.

Two lines in most languages:

// JavaScript
function fromUrlSafe(s) {
  const swapped = s.replace(/-/g, '+').replace(/_/g, '/');
  return swapped + '='.repeat((4 - swapped.length % 4) % 4);
}
# Python has it built in
import base64
base64.urlsafe_b64decode(token + '=' * (-len(token) % 4))

Many libraries tolerate missing padding already, so try decoding before you add it. Node accepts base64url as an encoding name from version 16 and needs no manual work at all.

PHP and browser JavaScript both need the manual swap. Neither has a built-in URL-safe function.

How to Spot Which Variant You Have

Look for the giveaway characters. A dash or underscore means base64url. A plus or slash means standard.

The awkward case is a string containing none of them, which is common for short inputs. Both decoders then produce identical output and it does not matter.

Our Base64 to file decoder detects the variant and converts it before decoding, then tells you it did so. That message alone has saved a few confusing support threads.

When to Reach for Each One

  • Anywhere in a URL — path, query string, or fragment — use base64url.
  • JWTs and OAuth — base64url, and it is mandatory rather than a preference.
  • Filenames — base64url, though case-insensitive filesystems can still collide.
  • Email and MIME — standard Base64, with 76-character line wrapping.
  • Data URIs — standard Base64, since the browser expects it.
  • JSON payloads — either works; standard is the convention.

The general rule we give: if the string will ever appear in a URL, use the URL-safe variant from the start. Converting later is easy but remembering to is not.

Wrapping Up

Base64URL vs Base64 comes down to two characters and some missing equals signs. The algorithm is the same and the decoded bytes are identical.

When a decoder rejects a string that looks valid, check for dashes and underscores first. That one glance solves it more often than anything else.

More background in what Base64 encoding is, and working code for every language in encoding and decoding Base64 in JavaScript, Python and PHP.

Frequently Asked Questions

How do I tell which variant a string uses?

+
Look for the giveaway characters. Dashes or underscores mean base64url; plus or slash means standard. A short string may contain none of them, in which case both decoders work identically.

Why does JWT drop the padding?

+
Because equals signs get percent-encoded in URLs, turning each one into three characters. A JWT often lives in a query string or a cookie, so RFC 7515 removed padding entirely and decoders infer the length from the segment.

Can I just add the equals signs back manually?

+
Yes, and it is a two-line fix. Pad the string with equals until its length divides by four. Most libraries will accept it after that, though many handle the missing padding on their own.

Is base64url a different algorithm?

+
No, the underlying maths is identical. Only the last two characters of the 64-character alphabet change, values 62 and 63. Decoded output is byte-for-byte the same either way.

Why does my JWT decode as gibberish in a standard decoder?

+
You probably fed it the whole token. A JWT is three separate base64url segments joined by dots, and the dots are not valid Base64. Split on the dots and decode each part on its own.

Does base64url work inside a filename?

+
Better than standard Base64, since the slash would be read as a directory separator. It is still not fully safe, because case-insensitive filesystems can collide two names that differ only in capitalisation.

What about Base64 in a URL path rather than a query string?

+
Same problem, sometimes worse. A slash creates a phantom path segment and some servers normalise or reject it before your code ever runs. Use base64url anywhere in a URL, not just the query.

Are there other Base64 variants?

+
Several, and they mostly exist for one hostile environment each. IMAP uses a modified form with comma instead of slash, and some XML schemes have their own. RFC 4648 defines the two that matter in practice.

Does converting between variants risk data loss?

+
None at all — it is a pure character substitution. Swap dash for plus and underscore for slash, restore the padding, and the decoded bytes are identical.

Why did my signature verification fail after re-encoding?

+
Because you changed the exact bytes that were signed. A JWT signature covers the literal base64url text of the header and payload, so re-encoding with padding or different characters invalidates it. Verify against the original string.

Tags

#Base64#JWT#URLs#Encoding