Skip to main content

The Basic Auth Header Explained: How Authorization Basic Actually Works

How HTTP Basic Authentication builds the Authorization Basic header, why username:password is only Base64-encoded (not encrypted), and why HTTPS is mandatory.

Published By Li Lei
#http #authentication #base64 #api #security

The Basic Auth Header Explained: How Authorization Basic Actually Works

HTTP Basic Authentication is the oldest credential scheme on the web, and it is still everywhere: internal admin panels, scraping APIs, CI registries, router config pages, and the quick-and-dirty endpoints nobody ever got around to upgrading. It is also the scheme people most often misread, because it looks like it hides your password when it does no such thing. This post walks through exactly what the Authorization: Basic header contains, how the token is built, and the one rule you cannot break: never send it over plain HTTP.

What the server actually receives

When a client uses Basic auth, it sends a single request header that looks like this:

Authorization: Basic YWxhZGRpbjpvcGVuc2VzYW1l

That is the whole mechanism. There is no challenge-response handshake, no nonce, no signature. The word Basic names the scheme, and the long string after it is the credential. A server that wants Basic auth will, on an unauthenticated request, reply with 401 Unauthorized and a WWW-Authenticate: Basic realm="..." header. The client then resends the request with the Authorization line above attached.

The defining detail of Basic auth is this: the header carries Authorization: Basic base64(username:password). The username and password are joined by a single colon, those bytes are Base64-encoded, and the result is prefixed with Basic . Base64 is an encoding, not a cipher. There is no key, no secret, nothing to crack. Anyone who captures that header can decode it back to the plaintext password in one step. That single fact drives every other decision in this post.

Building the token by hand

Take the canonical example from RFC 7617: username aladdin, password opensesame.

  1. Join them with a colon: aladdin:opensesame
  2. Encode those UTF-8 bytes as Base64: YWxhZGRpbjpvcGVuc2VzYW1l
  3. Prefix the scheme: Authorization: Basic YWxhZGRpbjpvcGVuc2VzYW1l

You can verify step 2 in any terminal:

$ printf '%s' 'aladdin:opensesame' | base64
YWxhZGRpbjpvcGVuc2VzYW1l

That matches what a Basic Auth header generator produces. The only place this trips people up is the colon: it is the separator, so the username may not contain one. The password can contain as many colons as it likes, because a well-behaved decoder splits on the first colon only and treats everything after it as the password. So Authorization: Basic for db:p:a:ss correctly yields username db, password p:a:ss.

One more detail that bites non-English teams: encode the user:password string as UTF-8 before you Base64 it. RFC 7617 sets UTF-8 as the default charset, and modern servers expect it, so a username like 用户名 or a password with an emoji encodes cleanly. If your shell's locale mangles non-ASCII characters into Latin-1, the token will silently differ from what the server stored, and the login fails with no useful error.

Base64 is not encryption

I want to be blunt about this because it is the mistake I see most. Base64 exists to move binary data through text-only channels safely. It is fully reversible by design and requires no secret. If you paste YWxhZGRpbjpvcGVuc2VzYW1l into any decoder, you get aladdin:opensesame back instantly.

So Basic auth gives you exactly zero confidentiality on its own. The password is right there in every request, in a form a five-year-old script can reverse. The thing that makes Basic auth safe in practice is the transport underneath it. Over HTTPS, TLS encrypts the entire request — method, path, headers, and body — before it leaves your machine. An attacker on the network sees an opaque blob, not your Authorization line. Over plain HTTP, that same line travels in the clear, and anyone on the path (a coffee-shop router, a compromised proxy, a curious sysadmin) can read the password verbatim.

The rule is therefore simple and non-negotiable: Basic auth only over HTTPS, never over HTTP. If a service offers Basic auth on an http:// URL, treat the credentials as already leaked.

Testing a protected API without a helper library

Where Basic auth still earns its keep is fast API testing. You do not need an auth library or an SDK to hit a protected endpoint — you need one correct header. curl gives you two ways to send it:

# Shorthand: curl builds the header for you
curl -u 'aladdin:opensesame' https://api.example.com/v1/status

# Explicit: paste a header you already encoded
curl -H 'Authorization: Basic YWxhZGRpbjpvcGVuc2VzYW1l' https://api.example.com/v1/status

Both send the identical wire format. The -u form is shorter; the -H form is the one I reach for when a colleague has already handed me an encoded token, or when I want to drop the exact header into a Postman tab or an HTTP client config. The explicit header is also the friendlier thing to share in chat, because it does not echo the raw password back as a separate visible argument.

When a request is failing and all you have is a header line out of a proxy log, run the process backwards. Decode Authorization: Basic YWxhZGRpbjpvcGVuc2VzYW1l and you can confirm whether the client is actually sending the account you think it is, instead of a stale token from an old deploy. That round-trip — encode to set up the call, decode to debug it — is most of what day-to-day Basic auth work looks like.

Why I generate the header locally

Here is the part I feel strongly about. The first few times I needed a Basic auth header, I searched for an online encoder, typed in a real staging password, and hit a server I had never heard of. That is a bad habit dressed up as convenience. Because the token is reversible, pasting a real credential into a remote site hands that site your password in plaintext, full stop. So now I generate the format locally with a throwaway value like aladdin:opensesame, confirm the header shape looks right, and only then swap in the real secret on my own machine. A good generator runs entirely in the browser tab — the encoding happens through the native btoa and TextEncoder APIs, nothing is sent anywhere, and the password is deliberately kept out of any shareable link. That is the only kind of online encoder I trust with anything close to a real credential, and even then I lean on the throwaway-first habit.

When to reach for something else

Basic auth is fine for low-stakes internal tools behind HTTPS, but it has no concept of expiry, scopes, or revocation — every request carries the full password. For anything user-facing or long-lived, token-based schemes such as bearer tokens or JWTs are the better fit. And if your job is the server side — generating the credential file an Nginx or Apache auth_basic block checks against — you want hashed entries, not a raw token; an htpasswd file generator produces those salted hashes so the password is never stored in the clear on disk.

The mental model to keep: the Authorization: Basic header is just base64(username:password), encoding and not encryption, readable by anyone who sees it. Build it locally, test with curl, and run it only over HTTPS, and Basic auth does exactly the small, honest job it was designed for.


Made by Toolora · Updated 2026-06-13