🧰Daily Toolbox
← All guides
base64

Base64 Encoding: The 3 Silent Bugs That Corrupt Your Data

2026-09-05 · 6 min read

You copy-pasted a Base64 string from Postman into your code. The API returns `400 Bad Request`. You check the encoding—looks fine. You Base64-decode it locally—works perfectly. You deploy to production. It fails again.

The problem? Base64 has three invisible gotchas that break silently across tools, languages, and platforms. Here's what nobody tells you.

The 3 Silent Traps

### 1. URL-Safe vs. Standard Base64 (The JWT Trap)

You've seen this error:

```
Invalid token: illegal base64 data at input byte 8
```

The problem: Standard Base64 uses `+` and `/`. URL-safe Base64 uses `-` and `_`.

JWTs must use URL-safe encoding (RFC 7515). If you Base64-encode a JWT signature with the standard alphabet, it breaks in URLs:

```javascript
// WRONG (standard Base64)
const sig = btoa(hash); // produces: 3f/8Kq+vLw==
// Browser converts / to %2F in URL → breaks signature

// RIGHT (URL-safe)
const sig = btoa(hash).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
// produces: 3f_8Kq-vLw
```

Real case: Stripe webhooks fail silently when you verify signatures with wrong Base64 variant (they use URL-safe; most libraries default to standard).

The fix:
- JWTs, URL params, filenames → URL-safe Base64 (`-` `_` no padding)
- Email attachments, data URIs, HTTP headers → Standard Base64 (`+` `/` with padding)

### 2. Padding Is Optional (But Your Parser Doesn't Know That)

Base64 requires padding with `=` to make the output length a multiple of 4. JavaScript's `atob()` rejects unpadded Base64, but Python accepts both.

Real case: You generate Base64 in Python, strip padding for "cleaner URLs," send it to a browser. JavaScript crashes.

The fix:

```javascript
// Safe decode (add padding if missing)
function safeAtob(str) {
return atob(str + '='.repeat((4 - str.length % 4) % 4));
}

safeAtob("SGVsbG8"); // ✅ "Hello"
```

### 3. Line Breaks Kill Everything (The MIME Trap)

MIME Base64 (RFC 2045) requires line breaks every 76 characters. PEM certificates, email attachments, and S/MIME all use this format. JavaScript's `atob()` doesn't auto-strip them.

Real case: Apple Push Notification certificates fail silently when you forget to strip `\n` before decoding.

The fix:

```javascript
function decodeBase64(str) {
return atob(str.replace(/[\r\n\s]/g, ''));
}
```

Production Checklist

| Use case | Variant | Padding | Characters |
|----------|---------|---------|------------|
| JWT, OAuth2 token | URL-safe | ❌ No | `A-Za-z0-9-_` |
| HTTP header | Standard | ✅ Yes | `A-Za-z0-9+/` |
| Email attachment | MIME | ✅ Yes + `\n` every 76 chars | `A-Za-z0-9+/` |

The Real-World Bug

Slack webhook signatures use HMAC-SHA256, Base64-encoded. Their docs don't say:

1. The signature is URL-safe Base64 (no `+` `/`)
2. Padding is stripped (no `=`)
3. If you decode with `atob()` without adding padding → silent auth bypass

Shopify had this exact bug in 2019 (CVE-2019-5418). Severity: Critical.

When NOT to Use Base64

Base64 increases size by 33%. Before you encode:

Don't use Base64 for:
- ❌ Database storage → Use `BYTEA` (PostgreSQL) or `VARBINARY` (MySQL)
- ❌ Large files → Use multipart upload or presigned URLs
- ❌ Cache keys → Use hex (smaller) or raw bytes

Summary

| Problem | Fix |
|---------|-----|
| URL-safe vs Standard | Use `-` `_` for URLs; `+` `/` for everything else |
| Missing padding | Add `=` padding: `str + '='.repeat((4 - str.length % 4) % 4)` |
| Line breaks | Strip `\r\n\s` before decode |

The golden rule: Never trust Base64 across language boundaries. Always validate, sanitize, and test both directions.

#base64#encoding#jwt#security

Try the free tools mentioned above

Open dev tools →