Back to Blog
2026-03-22

Base64 vs Base64URL: When URL Safety Matters

A practical guide to understanding Base64 and Base64URL encoding differences, common bug patterns, and secure implementation across JavaScript, Python, Go, and Java.

Base64 vs Base64URL: When URL Safety Matters

Threat Level: Medium — Using standard Base64 in URLs can lead to data corruption, open redirects, and JWT verification failures

If you've ever debugged a JWT that works in one context but fails in another, or wondered why your URL parameters get corrupted after Base64 encoding, you've likely encountered the subtle but critical differences between Base64 and Base64URL encoding. These two encoding schemes are nearly identical—until they're not, and that difference breaks production systems.

The Core Problem: URL-Safety

Standard Base64 encoding produces output containing three characters that are problematic in URLs: +, /, and =. These characters have special meanings in URLs or query string parameters, leading to data corruption or routing issues. Base64URL solves this by substituting URL-safe alternatives while maintaining the same 6-bit encoding scheme.

Character Comparison

Standard Base64 uses the alphabet A-Z, a-z, 0-9, +, /, and = for padding. Base64URL replaces + with - (hyphen), / with _ (underscore), and omits the = padding character. This ensures that the resulting encoded string is safe to use directly in URLs without further escaping.

| Standard Base64 | Base64URL | Decimal Value | |-----------------|-----------|---------------| | + | - (hyphen) | 62 | | / | _ (underscore) | 63 | | = | (omitted) | Padding |

Encoding Process

Both encodings follow identical 6-bit chunking but diverge at character selection and padding.

Example: Encoding "Hello"

Given input bytes [0x48, 0x65, 0x6c, 0x6c, 0x6f] ("Hello"), the binary representation is concatenated and split into 6-bit groups. Since the input length is not a multiple of 3, standard Base64 uses padding (=) to form full 4-character output blocks. However, for "Hello", the binary data is 0100100001100101011011000110110001101111, which splits into 6-bit groups mapping to SGVsbG8. This specific byte sequence doesn't require padding or problematic characters, so both Base64 and Base64URL produce the same SGVsbG8.

When They Diverge

Consider input that generates values 62 or 63, like >>>? (bytes [0xfc, 0xfc, 0xfc, 0x3f]).

  • Standard Base64: /Pz8/
  • Base64URL: _Pz8_

The + and / in standard Base64 become - and _ in Base64URL, making it safe for URLs.

Padding Differences

Standard Base64 Padding Rules

Base64 encoding produces 4 output characters for every 3 input bytes. When input isn't divisible by 3, padding ensures alignment. If the input length is 3n + 1 bytes, two = characters are added. If it's 3n + 2 bytes, one = character is added.

Base64URL Padding

Base64URL omits padding entirely. This simplification is crucial for URL safety, as = characters can interfere with query parameter parsing.

When to Use Each Encoding

Use Standard Base64 When:

  • Embedding in HTML/CSS (except URLs)
  • Email attachments (MIME)
  • Binary-to-text in configuration files
  • Data URIs (with proper escaping)
  • Protocols that expect RFC 2045 compliance

Use Base64URL When:

  • JWT header and payload segments
  • URL query parameters
  • URL path segments
  • File names in URLs
  • OAuth state parameters
  • WebSocket subprotocols
  • Any data transmitted via URL

JWT: The Primary Use Case

JWT (JSON Web Token) exclusively uses unpadded Base64URL encoding per RFC 7519. This means JWT libraries must decode Base64URL correctly without expecting padding. Implementations that assume standard Base64 padding will fail on valid JWTs.

JWT structure:

base64url(header).base64url(payload).base64url(signature)

A real JWT example:

eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.Signature

Notice the absence of +, /, and = in the encoded segments.

Common Bug Patterns

Bug 1: Using Standard Base64 for URL Parameters

Using standard Base64 in URLs is problematic because characters like + and / are interpreted differently by URL parsers, breaking the data.

BROKEN:

// Standard Base64 in URL parameter can corrupt data
const data = btoa('+/+'); // Produces '/+/+'
window.location.href = `/search?data=${data}`;

The URL becomes ?data=/+/, which browsers might not interpret correctly.

CORRECT:

// Using Base64URL for URL safety
function base64UrlEncode(str) {
  const base64 = btoa(unescape(encodeURIComponent(str)));
  return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
}
const urlSafeBase64 = base64UrlEncode(">>>?"); // Encodes problematic characters
window.location.href = `/search?data=${urlSafeBase64}`; // Safe URL: ?data=_Pz8_

Bug 2: Expecting Padding in Base64URL Decoding

JWT libraries or custom decoders must be prepared for the absence of padding in Base64URL.

BROKEN (Python):

import base64

# JWT segment (no padding)
jwt_payload = "eyJ1c2VyIjoiYWRtaW4ifQ"
# This fails: binascii.Error: Incorrect padding
base64.b64decode(jwt_payload)

CORRECT (Python):

import base64

def base64url_decode(input_str):
    # Add padding if necessary
    padding_needed = 4 - len(input_str) % 4
    if padding_needed != 4:
        input_str += '=' * padding_needed
    return base64.urlsafe_b64decode(input_str)

decoded = base64url_decode("eyJ1c2VyIjoiYWRtaW4ifQ")

Bug 3: Mixing Encodings in Cryptographic Operations

Using standard Base64 for one part of a JWT (like payload) and Base64URL for another (like signature) without proper handling can lead to signature verification failures or vulnerabilities. Always use Base64URL consistently for JWT segments.

Language-Specific Implementations

JavaScript / TypeScript

// Modern Node.js (v14.18+) has direct support
const encoded = Buffer.from(data).toString('base64url');
const decoded = Buffer.from(encoded, 'base64url').toString();

// Browser + Node compatible function
function base64UrlEncode(str) {
  const base64 = btoa(unescape(encodeURIComponent(str)));
  return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
}

Python

import base64

# Encoding (no padding)
encoded = base64.urlsafe_b64encode(b'hello world').rstrip(b'=').decode('ascii')

# Decoding (handles missing padding)
def base64url_decode(input_str):
    padding_needed = 4 - len(input_str) % 4
    if padding_needed != 4:
        input_str += '=' * padding_needed
    return base64.urlsafe_b64decode(input_str)

Go

import (
    "encoding/base64"
)

// Encoding (no padding)
encoded := base64.RawURLEncoding.EncodeToString(data)

// Decoding (no padding expected)
decoded, err := base64.RawURLEncoding.DecodeString(encoded)

Security Implications

URL Injection Attacks

Using standard Base64 in URLs can lead to:

  • Open redirects: + interpreted as space in URL parsing.
  • Parameter pollution: Special characters like / and = can break query strings.
  • Path traversal: / characters in Base64 can be interpreted as path separators.

Signature Bypasses

JWT implementations that accidentally use standard Base64 for signature verification but Base64URL for parsing can allow signature forgery.

Quick Reference Table

| Aspect | Standard Base64 | Base64URL | | :-------------- | :-------------- | :------------- | | Character 62 | + | - (hyphen) | | Character 63 | / | _ (underscore) | | Padding (=) | Required | Omitted | | Use in URLs | Requires escaping | Direct use | | JWT Support | No | Yes (required) | | RFC Reference | RFC 4648 §4 | RFC 4648 §5 |

Secure Local Base64 Encoding

Encode and decode sensitive strings without sending data over the network. Our local Base64 tool handles standard and URL-safe formats instantly in your browser.

Open Base64 Converter →

Conclusion

Understanding the difference between Base64 and Base64URL is essential for building robust web applications. While the differences are minimal—just three character substitutions—they can have significant security and functionality implications when dealing with JWTs, URL parameters, and web APIs.

Remember:

  • Use Base64 for internal storage and email.
  • Use Base64URL for anything that goes in a URL.
  • Always handle padding correctly when implementing custom decoders.
  • Use your language's built-in Base64URL functions when available.

📬 Stay Ahead of Threats Want more actionable security guides like this? Join 10,000+ developers in the OpSecForge Newsletter for weekly deep-dives into API security and DevSecOps.

Share this: