Understanding Base64 Encoding Edge Cases
Understanding Base64 Encoding Edge Cases
Base64 encoding converts binary data into ASCII text, making it safe to embed in JSON, URLs, emails, and other text-based formats. While the basics are simple, there are edge cases that trip up even experienced developers.
How Base64 Works
Base64 takes every 3 bytes of input and maps them to 4 ASCII characters from a 64-character alphabet (A-Z, a-z, 0-9, +, /). The = character is used for padding when the input length is not divisible by 3.
// Encoding in Node.js
const encoded = Buffer.from("Hello, World!").toString("base64");
// "SGVsbG8sIFdvcmxkIQ=="
// Decoding
const decoded = Buffer.from(encoded, "base64").toString("utf-8");
// "Hello, World!"
Edge Case: UTF-8 Multi-byte Characters
The most common Base64 bug involves non-ASCII characters. In browsers, btoa() only handles Latin-1 characters and throws on anything else.
// This throws in the browser:
btoa("Hellö Wörld"); // Works (Latin-1 chars happen to be OK)
btoa("Hello 🌍"); // DOMException: invalid character
// Correct approach for Unicode:
function utf8ToBase64(str) {
return btoa(encodeURIComponent(str).replace(
/%([0-9A-F]{2})/g,
(_, p1) => String.fromCharCode(parseInt(p1, 16))
));
}
In Node.js, use Buffer.from(str, "utf-8").toString("base64") which handles Unicode correctly.
Edge Case: URL-Safe Base64
Standard Base64 uses + and / which are special characters in URLs. URL-safe Base64 replaces them with - and _ and often drops the = padding.
// Standard: "SGVsbG8+V29ybGQ/IA=="
// URL-safe: "SGVsbG8-V29ybGQ_IA"
function toUrlSafeBase64(str) {
return Buffer.from(str)
.toString("base64")
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=+$/, "");
}
JWTs use URL-safe Base64 without padding for their header and payload segments. Forgetting this when manually inspecting tokens is a common mistake.
Edge Case: Newlines in Base64
Some encoders insert newlines every 76 characters (per the MIME specification). This is correct for email but will break JSON parsing or URL handling.
// MIME-style (with newlines):
// SGVsbG8sIFdvcmxkIQ==\n
// Always strip newlines when using Base64 in JSON or URLs:
const clean = encoded.replace(/\n/g, "");
Edge Case: Padding Sensitivity
Some decoders are strict about padding (the trailing = characters) while others are lenient. When interoperating between systems, always include padding to be safe.
// Add missing padding
function addPadding(base64) {
const pad = base64.length % 4;
if (pad === 2) return base64 + "==";
if (pad === 3) return base64 + "=";
return base64;
}
Base64 Is Not Encryption
A critical reminder: Base64 is encoding, not encryption. It provides zero security. Anyone can decode Base64 instantly. Never use it to "protect" sensitive data.
Conclusion
Base64 is deceptively simple. Handle UTF-8 correctly, choose between standard and URL-safe variants intentionally, watch for newline injection, and remember that padding matters for interoperability.