Base64 is one of those things developers use constantly — in JWTs, data URLs, API authentication — without always understanding what it actually does. Here's a clear explanation.
What Base64 is (and isn't)
Base64 is an encoding scheme, not encryption. It converts binary data into a string of 64 printable ASCII characters: A-Z, a-z, 0-9, +, and /, plus = for padding.
The purpose: safely represent binary data in contexts that only handle text. Email attachments, HTML data URLs, and HTTP headers are all text-based — Base64 lets you embed binary content (images, files) in those contexts without corruption.
If you need to encode or decode Base64 in the browser, the Base64 Encoder/Decoder handles both directions instantly.
How it works
Base64 works in three steps:
- Take the binary input as bytes (8 bits each)
- Group the bits into 6-bit chunks
- Map each 6-bit value (0–63) to one of the 64 characters
Example with "Man":
M = 01001101
a = 01100001
n = 01101110
Combined: 010011010110000101101110
Split into 6-bit groups: 010011 | 010110 | 000101 | 101110
Decimal: 19 | 22 | 5 | 46
Base64: T | W | F | u
Result: "TWFu"
Three bytes of input produce exactly four Base64 characters — a 33% size increase.
Padding
Input doesn't always divide evenly into 3-byte groups. = characters pad the output to a multiple of 4 characters:
- 1 remaining byte → 2 Base64 chars +
== - 2 remaining bytes → 3 Base64 chars +
= - 3 remaining bytes → 4 Base64 chars (no padding)
Base64 in JavaScript
// Browser — btoa() and atob()
const encoded = btoa('Hello, world!'); // "SGVsbG8sIHdvcmxkIQ=="
const decoded = atob('SGVsbG8sIHdvcmxkIQ=='); // "Hello, world!"
// Node.js — Buffer
const encoded = Buffer.from('Hello, world!').toString('base64');
// "SGVsbG8sIHdvcmxkIQ=="
const decoded = Buffer.from('SGVsbG8sIHdvcmxkIQ==', 'base64').toString('utf8');
// "Hello, world!"
Unicode caveat with btoa()
btoa() only handles characters with code points 0–255. For arbitrary Unicode strings:
// Fails for characters outside Latin-1
btoa('€') // throws InvalidCharacterError
// Fix: encode as UTF-8 first
function encodeBase64(str) {
return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g,
(_, p1) => String.fromCharCode(parseInt(p1, 16))
));
}
// Or in modern environments:
const encoder = new TextEncoder();
const bytes = encoder.encode('Hello, €');
const base64 = btoa(String.fromCharCode(...bytes));
Node.js Buffer handles UTF-8 correctly without this issue.
URL-safe Base64
Standard Base64 uses + and /, which have special meaning in URLs. URL-safe Base64 replaces them:
-
+→- -
/→_ -
=padding is often omitted
This variant is used in JWTs, OAuth tokens, and anywhere Base64 appears in URLs or HTTP headers.
function toBase64Url(base64) {
return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
}
function fromBase64Url(base64url) {
const padded = base64url.replace(/-/g, '+').replace(/_/g, '/');
const padding = padded.length % 4 === 0 ? '' : '='.repeat(4 - padded.length % 4);
return padded + padding;
}
Common uses
Data URLs (embedding images in HTML/CSS)
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA...">
.icon {
background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucy...');
}
This embeds the image directly in the HTML or CSS file — no extra HTTP request. Useful for tiny icons, but avoid for anything larger than ~1KB (base64 adds 33% overhead and can't be cached separately).
HTTP Basic Authentication
const credentials = btoa('username:password');
fetch('/api/data', {
headers: {
'Authorization': `Basic ${credentials}`
}
});
Basic Auth Base64-encodes the username:password string. Note: this is not encryption — it's trivially reversible. Always use HTTPS.
JWT tokens
The header and payload sections of a JWT are Base64Url-encoded JSON:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0In0.abc123
^--- header ---^ ^-payload-^ ^-signature-^
Decoding the header: atob('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9') → {"alg":"HS256","typ":"JWT"}
Email attachments (MIME)
Content-Type: application/pdf; name="document.pdf"
Content-Transfer-Encoding: base64
JVBERi0xLjQKJeLjz9MKMSAwIG9iago8PAovVHlwZSAvQ2F0YWxvZwov...
MIME uses Base64 to embed binary attachments in the text-based email format.
Base64 is not encryption
This is worth repeating. Base64 encoding is completely reversible without any key — it's a standard algorithm anyone can reverse. Never use it to protect sensitive data.
Using Base64 to "hide" a password or API key is security theater. An attacker who sees the encoded string can decode it in seconds.
For actual security: use encryption (AES for symmetric, RSA/ECDSA for asymmetric) or hashing (bcrypt/Argon2 for passwords, SHA-256+ for integrity checks).
Size trade-offs
Original: 100 bytes binary
Base64: 136 bytes (33% larger)
Original: 1 MB image
Base64: 1.37 MB
The 33% overhead is predictable: 3 input bytes → 4 output characters. For images and files, this overhead is why external files + HTTP caching is usually better than data URLs beyond small sizes.
Base64 exists to solve a specific problem: safely transmitting binary data through text-only channels. When you see it in JWTs, data URLs, or API credentials, that's what it's doing. It's not compression, not encryption — just a reliable way to represent bytes as printable text.
Top comments (0)