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.
Primary source: authoritative reference
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.
Try it locally
Convert Base64 or Base64URL in your browser
Encode or decode a sample without sending its contents to OpsecForge.
Open the Base64 Converter →The Core Problem: URL-Safety
Standard Base64 can produce +, /, and =. Those characters may require escaping or special handling in URL components. Base64URL substitutes a URL- and filename-safe alphabet while keeping the same 6-bit encoding scheme. Neither encoding encrypts or authenticates the underlying bytes.
Character Comparison
Standard Base64 uses the alphabet A-Z, a-z, 0-9, +, and /, with = as a pad character. RFC 4648 section 5 defines the URL- and filename-safe alphabet by replacing + with - and / with _. Padding may be omitted only when the specification that uses Base64URL permits it.
| 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"), standard Base64 produces SGVsbG8=. An unpadded Base64URL representation is SGVsbG8. The alphabets do not diverge for this input, but the padding convention can.
When They Diverge
Consider the ASCII input 00? (bytes [0x30, 0x30, 0x3f]).
- Standard Base64:
MDA/ - Base64URL:
MDA_
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 applications often omit padding, but RFC 4648 requires padding unless the referring specification explicitly says otherwise. Follow the protocol you are implementing; JWT compact serialization is one common unpadded use.
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
- Protocol fields that explicitly require the RFC 4648 URL-safe alphabet
JWT: The Primary Use Case
JWT compact serialization uses Base64URL-encoded segments without padding, as specified by RFC 7519 and its underlying JOSE rules. Use a maintained JWT library for parsing and signature verification; Base64URL decoding alone does not validate a token.
JWT structure:
base64url(header).base64url(payload).base64url(signature)
A synthetic JWT-shaped 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('00?'); // Produces 'MDA/'
window.location.href = `/search?data=${data}`;
The / requires the URL-component handling expected by the receiving application.
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("00?");
window.location.href = `/search?data=${urlSafeBase64}`; // ?data=MDA_
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
Encoding is not a security control
Choosing the wrong alphabet can cause parsing or interoperability failures. Choosing the right alphabet does not make untrusted data safe, prevent injection, or verify a JWT signature. Validate the decoded data for its destination and use the protocol's normal authentication and authorization controls.
Quick Reference Table
| Aspect | Standard Base64 | Base64URL |
| :-------------- | :-------------- | :------------- |
| Character 62 | + | - (hyphen) |
| Character 63 | / | _ (underscore) |
| Padding (=) | Required unless specified otherwise | Protocol-dependent |
| Use in URLs | May require escaping | URL-safe alphabet |
| JWT Support | No | Yes (required) |
| RFC Reference | RFC 4648 §4 | RFC 4648 §5 |
Local Base64 text conversion
Encode and decode sample UTF-8 text with standard or URL-safe Base64 in the loaded browser page. Do not treat encoded output as encrypted or safe to disclose.
Open Base64 Converter →Conclusion
Understanding the difference between Base64 and Base64URL helps prevent avoidable parsing and interoperability bugs in JWTs, URL parameters, and web APIs.
Remember:
- Use Base64 for internal storage and email.
- Use Base64URL when the receiving protocol requires its URL-safe alphabet.
- Always handle padding correctly when implementing custom decoders.
- Use your language's built-in Base64URL functions when available.
📬 Stay Ahead of Threats Continue with the JWT Decoder for JWT segments, the URL Encoder for query components, or browse the complete tools center.