HTTP Basic Auth & the Authorization Header
HTTP Basic Authentication encodes a username and password as Base64 and sends them in the Authorization header. It is simple to implement but easy to misuse. Here is how it works and how to do it safely.
How HTTP Basic Authentication works (RFC 7617)
HTTP Basic Authentication is one of the oldest and simplest authentication schemes on the web. It was originally defined in RFC 2617 and is now specified in RFC 7617. The idea is straightforward: the client sends a username and password with every request, packaged in a single HTTP header. There are no sessions, no cookies, and no token exchange — just credentials presented on each round trip.
The typical challenge-response flow looks like this. A client requests a protected resource without credentials, and the server replies with 401 Unauthorized plus a WWW-Authenticate header naming the scheme and a realm (a label describing the protected area). The client then retries the request, this time including an Authorization header carrying the Base64-encoded credentials.
> GET /secret HTTP/1.1 > Host: api.example.com < HTTP/1.1 401 Unauthorized < WWW-Authenticate: Basic realm="Restricted Area", charset="UTF-8" > GET /secret HTTP/1.1 > Host: api.example.com > Authorization: Basic dXNlcjpwYXNz < HTTP/1.1 200 OK
Because the scheme is stateless, the server must validate the credentials on every single request. Most clients optimize this by sending the Authorization header proactively after the first challenge, rather than waiting for repeated 401 responses. The realm value lets a single server expose multiple protected areas, each requiring its own credentials.
RFC 7617 added an explicit charset parameter so servers can signal that they expect UTF-8 encoded credentials. This matters when usernames or passwords contain non-ASCII characters — always encode the username:password string as UTF-8 bytes before Base64-encoding.
The Authorization header format: Basic base64(username:password)
The format of the header is precise and easy to reproduce. The value is the word Basic, a single space, and then the Base64 encoding of the username and password joined by a colon:
Authorization: Basic base64(username ":" password)
For example, with the username user and the password pass, you join them into the string user:pass, encode those 9 bytes as Base64, and get dXNlcjpwYXNz. The full header becomes:
Authorization: Basic dXNlcjpwYXNz
There are two important rules that trip people up. First, the colon is the delimiter, so a username may not contain a colon — a password can, since everything after the first colon is treated as the password. Second, the entire username:password string is encoded as one unit; you do not encode the username and password separately.
Do not add line breaks or whitespace inside the Base64 value. Some encoders (notably older command-line tools) wrap output at 76 characters by default. A wrapped credential string will produce a malformed header and a confusing 401. Strip newlines before building the header.
Encode credentials in curl, JavaScript, Python, and .NET/C#
Every HTTP client and platform offers a way to build the Basic Auth header. Here are working examples in four common environments.
curl has first-class support through the -u / --user flag, which builds the header for you. You can also set it manually:
# Let curl encode the credentials for you curl -u user:pass https://api.example.com/secret # Or set the header explicitly curl -H "Authorization: Basic dXNlcjpwYXNz" https://api.example.com/secret
JavaScript (in the browser or any runtime with btoa) uses btoa() to Base64-encode the credential string before passing it to fetch:
const username = "user";
const password = "pass";
const header = "Basic " + btoa(`${username}:${password}`);
const res = await fetch("https://api.example.com/secret", {
headers: { Authorization: header }
});
btoa() operates on Latin-1 (binary) strings, so it breaks on multibyte UTF-8 characters. If your credentials may contain non-ASCII characters, encode them first: btoa(new TextEncoder().encode(s).reduce((a, b) => a + String.fromCharCode(b), "")), or use a Buffer in Node.js.
Node.js avoids the btoa charset pitfalls entirely by using Buffer, which handles UTF-8 correctly:
const creds = Buffer.from("user:pass", "utf-8").toString("base64");
const header = `Basic ${creds}`;
Python uses the base64 module. Note the explicit .encode() to bytes and .decode() back to a string:
import base64
raw = "user:pass".encode("utf-8")
token = base64.b64encode(raw).decode("ascii")
headers = {"Authorization": f"Basic {token}"}
# Or with the requests library, which does this for you:
import requests
requests.get("https://api.example.com/secret", auth=("user", "pass"))
.NET / C# builds the header with Convert.ToBase64String and attaches it to an HttpClient request:
using System.Net.Http.Headers;
using System.Text;
var bytes = Encoding.UTF8.GetBytes("user:pass");
var token = Convert.ToBase64String(bytes);
var client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Basic", token);
var response = await client.GetAsync("https://api.example.com/secret");
Why Base64 is NOT encryption — security implications
This is the single most important thing to understand about Basic Auth: Base64 is an encoding, not encryption. It has no key, no secret, and provides zero confidentiality. Its only purpose is to represent arbitrary bytes — including non-printable characters and binary data — as a safe subset of ASCII so they can travel cleanly inside an HTTP header. Anyone can decode it instantly with no special knowledge.
Decoding the example header takes one command:
$ echo "dXNlcjpwYXNz" | base64 --decode user:pass
The same is true in any language — atob("dXNlcjpwYXNz") in JavaScript, base64.b64decode(...) in Python. There is no brute force, no cracking, no compute cost. If an attacker captures the header, they have your plaintext credentials immediately. Treat a Basic Auth header exactly as you would treat the raw password itself.
Never log Authorization headers, never put credentials in URLs (the deprecated https://user:pass@host form leaks into history, logs, and referrer headers), and never store the encoded token thinking it is "hashed." Base64 is reversible and provides no protection whatsoever.
HTTPS is mandatory: credentials are trivially decoded without TLS
Because the credentials are sent in cleartext (Base64 being equivalent to cleartext), Basic Auth is only acceptable over a connection that encrypts the transport. In practice that means HTTPS / TLS is non-negotiable. TLS encrypts the entire HTTP request — method, path, headers, and body — so an eavesdropper on the network sees only ciphertext and cannot read the Authorization header.
Over plain HTTP, anyone positioned between the client and server — on the same Wi-Fi network, at an ISP, on a compromised router, or running a man-in-the-middle proxy — can capture the request and read the credentials with the one-line decode shown above. Worse, since Basic Auth resends the credentials on every request, a single intercepted request is enough, and the exposure repeats endlessly for the life of the session.
Defense in depth: serve your API only over HTTPS, send Strict-Transport-Security (HSTS) so browsers refuse to downgrade to HTTP, and configure servers to reject Basic Auth on non-TLS connections entirely. If you cannot guarantee TLS end to end, do not use Basic Auth.
Basic Auth vs Bearer tokens and when to use each
Modern APIs more often use the Authorization: Bearer <token> scheme, where the token is typically an OAuth 2.0 access token or a JWT. The header format looks superficially similar, but the security model is very different. A bearer token is usually short-lived, scoped to specific permissions, and revocable without changing the user's actual password.
The key differences:
- Credential exposure. Basic Auth sends your real, long-lived password on every request. A leaked bearer token expires and can be revoked; a leaked password compromises the account until it is changed everywhere.
- Scope. Bearer tokens can be issued with limited scopes (read-only, a single resource). Basic Auth grants whatever the account itself can do.
- Rotation. Tokens rotate automatically via refresh flows. Passwords require manual changes that break every integration using them.
- Statelessness. Both are stateless on the wire, but JWTs can carry claims the server validates without a database lookup.
So when is Basic Auth still the right choice? It is genuinely useful for server-to-server traffic on a trusted network, internal tooling and health-check endpoints, quick prototypes, and protecting low-risk resources behind a reverse proxy or CDN. It is also still the standard way to authenticate against many package registries and CI systems using a personal access token in place of a password. For anything user-facing, multi-tenant, or security-sensitive, prefer OAuth 2.0 bearer tokens.
A common hybrid pattern: use Basic Auth where the "username" is a client ID and the "password" is an API key or token. You get the simplicity of the header format with a credential you can rotate and revoke independently of any human password.
FAQ
Is Base64 in Basic Auth secure?
No. Base64 encoding is trivially reversible — anyone who intercepts the Authorization header can decode the credentials instantly. Basic Auth is only safe when used exclusively over HTTPS (TLS), which encrypts the entire request including headers.
How do I create a Basic Auth header in JavaScript?
Use btoa() to encode: const header = "Basic " + btoa(username + ":" + password). Then set it as fetch("url", { headers: { Authorization: header } }).
What is the format of the Authorization header for Basic Auth?
Authorization: Basic <credentials> where credentials is the Base64 encoding of username:password (colon-separated). Example: Authorization: Basic dXNlcjpwYXNz
Try Base64 encoding and decoding instantly
Paste any string or file — base64.dev auto-detects and converts it instantly.
Open base64.dev →