Base64URL vs. Base64: JWT Decoding Differences Explained + Quick Fixes
Is your Base64 token failing? Understand the critical differences between Base64 and Base64URL in JWT decoding. Use our tool to troubleshoot encoding/decoding errors now.
JWT segments (header and payload) are Base64URL-encoded. If your decoder assumes standard Base64, you'll see errors like "Incorrect padding" or "Invalid character". Understanding why Base64URL exists and how to normalize it prevents frustrating decode failures.
Why Base64URL exists
JWTs are designed to be URL-safe. Standard Base64 uses characters (+, /) that have special meaning in URLs and require encoding. Base64URL replaces these with URL-safe alternatives (-, _) so tokens can be:
- Passed in URL query parameters without encoding
- Included in HTTP headers without escaping
- Embedded in HTML without breaking parsing
The problem: Most programming languages have Base64 decoders, but they expect standard Base64 format. When you try to decode Base64URL with a standard Base64 decoder, it fails.
Base64 vs Base64URL: The differences
Character mapping
| Standard Base64 | Base64URL | Why changed |
|---|---|---|
+ |
- |
+ is encoded as %2B in URLs |
/ |
_ |
/ is a path separator in URLs |
= (padding) |
Often omitted | Padding can be inferred from length |
Visual examples: Character set differences
Example 1: Standard Base64 with + character
Standard Base64: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9+signature
Base64URL: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9-signature
↑ ↑
Same characters + becomes -
Example 2: Standard Base64 with / character
Standard Base64: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9/signature
Base64URL: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9_signature
↑ ↑
Same characters / becomes _
Example 3: Padding differences
Standard Base64: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9==
Base64URL: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9
↑
Padding (=) omitted
Key visual differences:
- Character replacement:
+→-and/→_make tokens URL-safe - Padding removal: Base64URL often omits
=padding characters - Length variation: Base64URL segments may not be multiples of 4 (without padding)
Use our JWT Decoder to see these differences in action - paste a token and watch how it handles Base64URL normalization automatically.
Why decoding fails
Error 1: "Invalid character"
Why it happens:
Your decoder encounters - or _ which aren't valid Base64 characters.
Example:
// This fails
Buffer.from('eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9', 'base64')
// Error: Invalid character
// Because Base64URL might have: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9
// Which contains characters that standard Base64 doesn't recognize
How to fix: Convert Base64URL to Base64 before decoding.
Error 2: "Incorrect padding"
Why it happens:
Base64 requires the input length to be a multiple of 4. Base64URL often omits padding (=), so the length might not be a multiple of 4.
Example:
// Base64URL without padding
const segment = 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9';
// Length: 36 (not a multiple of 4)
// This fails
Buffer.from(segment, 'base64')
// Error: Incorrect padding
How to fix:
Add padding (=) until the length is a multiple of 4.
Normalization function explained
Here's a complete normalization function with error handling:
function normalizeBase64URL(str) {
if (!str || typeof str !== 'string') {
throw new Error('Input must be a non-empty string');
}
// Step 1: Replace URL-safe characters with standard Base64 characters
str = str.replace(/-/g, '+').replace(/_/g, '/');
// Step 2: Add padding if needed
// Base64 requires length to be multiple of 4
const padding = (4 - (str.length % 4)) % 4;
str += '='.repeat(padding);
return str;
}
// Usage
function decodeJWT(token) {
const [headerSegment, payloadSegment] = token.split('.');
const decodeSegment = (segment) => {
const normalized = normalizeBase64URL(segment);
const decoded = Buffer.from(normalized, 'base64').toString('utf8');
return JSON.parse(decoded);
};
return {
header: decodeSegment(headerSegment),
payload: decodeSegment(payloadSegment)
};
}
Why this works:
- Character replacement converts Base64URL to Base64 format
- Padding ensures the decoder receives valid Base64
- The decoder can now process it normally
Real-world examples
Example 1: Node.js decoding
// Token segment (Base64URL)
const segment = 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9';
// Normalize
const normalized = normalizeBase64URL(segment);
// Result: 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9' (adds padding if needed)
// Decode
const decoded = Buffer.from(normalized, 'base64').toString('utf8');
// Result: '{"alg":"RS256","typ":"JWT"}'
// Parse JSON
const header = JSON.parse(decoded);
// Result: { alg: 'RS256', typ: 'JWT' }
Example 2: Browser decoding
// Browser doesn't have Buffer, use atob
function normalizeBase64URL(str) {
str = str.replace(/-/g, '+').replace(/_/g, '/');
const padding = (4 - (str.length % 4)) % 4;
return str + '='.repeat(padding);
}
function decodeSegment(segment) {
const normalized = normalizeBase64URL(segment);
const decoded = atob(normalized);
return JSON.parse(decoded);
}
// Usage
const [headerSegment, payloadSegment] = token.split('.');
const header = decodeSegment(headerSegment);
const payload = decodeSegment(payloadSegment);
Example 3: Python decoding
import base64
import json
def normalize_base64url(s):
# Replace URL-safe characters
s = s.replace('-', '+').replace('_', '/')
# Add padding
padding = (4 - len(s) % 4) % 4
return s + '=' * padding
def decode_segment(segment):
normalized = normalize_base64url(segment)
decoded = base64.b64decode(normalized).decode('utf-8')
return json.loads(decoded)
# Usage
header_segment, payload_segment, _ = token.split('.')
header = decode_segment(header_segment)
payload = decode_segment(payload_segment)
Common symptoms and fixes
Symptom: "Invalid character" error
What you see:
Error: Invalid character in base64 encoding
Why it happens:
Decoder encountered - or _ which aren't valid Base64 characters.
How to fix:
// Before decoding, normalize
const normalized = segment.replace(/-/g, '+').replace(/_/g, '/');
const decoded = Buffer.from(normalized, 'base64');
Prevention: Always normalize Base64URL before decoding, or use our JWT Encoder/Decoder which handles this automatically.
Need more help? If decoding errors persist, check our complete troubleshooting guide for invalid JWT errors which covers decode failures, verification issues, and common pitfalls.
Symptom: "Incorrect padding" error
What you see:
Error: Incorrect padding
Why it happens: Base64URL segment length isn't a multiple of 4 (padding was omitted).
How to fix:
// Add padding
while (segment.length % 4) {
segment += '=';
}
const decoded = Buffer.from(segment, 'base64');
Prevention: Use the normalization function above which handles padding automatically.
Symptom: Header decodes but payload fails
What you see: Header decodes fine, but payload throws an error.
Why it happens: One segment might have been normalized while the other wasn't, or one segment has different Base64URL characteristics.
How to fix:
// Normalize both segments consistently
const normalize = (s) => {
s = s.replace(/-/g, '+').replace(/_/g, '/');
while (s.length % 4) s += '=';
return s;
};
const header = JSON.parse(Buffer.from(normalize(headerSegment), 'base64').toString());
const payload = JSON.parse(Buffer.from(normalize(payloadSegment), 'base64').toString());
Prevention: Use a consistent normalization function for all segments.
Why libraries handle this automatically
Most JWT libraries (like jsonwebtoken in Node.js) handle Base64URL normalization internally. They know JWTs use Base64URL and convert it automatically.
When you need manual normalization:
- Building custom decoders
- Using raw Base64 decoding APIs
- Debugging decode failures
- Working with tokens in non-JWT contexts
Best practices
1. Use JWT-aware libraries when possible
Good:
const jwt = require('jsonwebtoken');
const decoded = jwt.decode(token); // Handles Base64URL automatically
Why: Libraries handle normalization, padding, and edge cases for you.
2. Normalize before raw decoding
If you must decode manually:
function safeDecode(segment) {
const normalized = normalizeBase64URL(segment);
return Buffer.from(normalized, 'base64').toString('utf8');
}
3. Test with real tokens
Use our JWT Encoder/Decoder to see how real tokens decode, then replicate that logic in your code.
4. Handle errors gracefully
function decodeSegment(segment) {
try {
const normalized = normalizeBase64URL(segment);
const decoded = Buffer.from(normalized, 'base64').toString('utf8');
return JSON.parse(decoded);
} catch (error) {
if (error.message.includes('Invalid character')) {
throw new Error('Base64URL normalization failed. Check for invalid characters.');
}
if (error.message.includes('padding')) {
throw new Error('Padding error. Ensure segment length is multiple of 4.');
}
throw error;
}
}
Debugging workflow
When you encounter Base64URL decode errors:
- Inspect the segment - Use our JWT Encoder/Decoder to see if it decodes there
- Check character set - Look for
-or_that need conversion - Verify padding - Ensure length is multiple of 4
- Normalize consistently - Apply same normalization to all segments
- Test incrementally - Decode header first, then payload
Tools that help
Browser tool
Our JWT Encoder/Decoder handles Base64URL normalization automatically:
- No manual conversion needed - Automatically normalizes Base64URL to Base64
- Instant decoding - See header and payload immediately with error highlighting
- Client-side processing - Your tokens stay private, no server transmission
- Visual feedback - See exactly how Base64URL segments are processed
Quick troubleshooting: If your token fails to decode elsewhere, paste it into our tool to verify the Base64URL format is correct. The tool will show you the normalized segments and decoded content instantly.
Command-line debugging
# Decode Base64URL segment manually
echo 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9' | \
sed 's/-/+/g; s/_/\//g' | \
awk '{while(length($0)%4)$0=$0"="}1' | \
base64 -d
Next steps
- Try decoding a token with our JWT Encoder/Decoder to see Base64URL in action
- Learn about safe JWT decoding practices before verification
- Read our troubleshooting guide for common decode errors
- Understand Base64 encoding basics for deeper context
Try JWT Encoder/Decoder Now
Ready to put this into practice? Use our free JWT Encoder/Decoder tool. It works entirely in your browser with no signup required.
Launch JWT Encoder/DecoderFrequently Asked Questions
Q Why does adding padding fix decoding?
JWTs use Base64URL without padding; some decoders expect = padding. Normalizing fixes decoding.
Related Articles
JWT Tokens Explained - Complete Guide to JSON Web Tokens
Learn everything about JWT tokens, how they work, when to use them, and best practices for secure implementation in your applications.
Understanding Base64 Encoding - Complete Guide
Learn everything about Base64 encoding, how it works, when to use it, and best practices for encoding and decoding binary data in text formats.
Fix “Invalid JWT” Errors — Common Causes and Checks
Troubleshoot invalid JWT errors with a systematic checklist for decoding and validation.