A colon in the username cannot work: the server splits user:password at the first colon.
What you type stays in this tab. The page builds the header with JavaScript and makes no request with it.
Build an HTTP Basic Authorization header from a username and password, or paste one in and get the credentials back. You also get ready-to-run curl, fetch, Python and wget lines, with shell quoting that survives a password containing an apostrophe.
A colon in the username cannot work: the server splits user:password at the first colon.
What you type stays in this tab. The page builds the header with JavaScript and makes no request with it.
username: Aladdin
password: open sesame Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
Join the username and password with a colon, encode that string as
UTF-8 bytes, Base64 the bytes, and put Basic and a space
in front. That is the whole scheme in RFC 7617. There is no hashing, no nonce and
no timestamp, so the same credentials always produce the same
header, and one line of code decodes it. The Decode mode here does
that.
Encode to UTF-8 before Base64.
Hand-rolled versions usually break at this step. In a browser,
btoa("user:pässword") either throws or encodes Latin-1
bytes, depending on the character, and the server then sees a
different password. RFC 7617 lets a server announce
charset="UTF-8" in its challenge; most modern servers assume it. This page always encodes UTF-8, and when decoding
it falls back to Latin-1 and says so if the bytes are not valid UTF-8.
Use Basic auth only over HTTPS.
With that in place it works well for a machine client talking to an
API, a staging site behind a password, or a webhook receiver. The credentials are sent with every request, so keep
them out of logs: many proxies and APM tools record headers by
default. Avoid putting them in the URL
(https://user:pass@host/): some browsers block it, and
the URL ends up in shell history, bookmarks and server logs.
There is no logout, since the browser caches the credentials until it is closed, and no protection against guessing beyond whatever rate limiting you add. For end users, use a login form with sessions or tokens instead.
function basicAuth(user, pass) {
// btoa() only takes Latin-1, so go through UTF-8 bytes first
const bytes = new TextEncoder().encode(user + ":" + pass);
return "Basic " + btoa(String.fromCharCode(...bytes));
}
console.log(basicAuth("Aladdin", "open sesame")); // Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
// Sending it (fetch rejects credentials embedded in the URL)
// await fetch("https://api.example.com/", { headers: { Authorization: basicAuth("user", "pass") } });
// Decoding on the server (Node.js)
function parseBasic(header) {
const m = /^Basic\s+(.+)$/i.exec(header || "");
if (!m) return null;
const text = Buffer.from(m[1], "base64").toString("utf8");
const i = text.indexOf(":");
return i < 0 ? null : { user: text.slice(0, i), pass: text.slice(i + 1) };
}
console.log(parseBasic("Basic dGVzdDoxMjPCow==")); // { user: 'test', pass: '123£' } In Node.js alone, Buffer.from(user + ":" + pass).toString("base64") does the UTF-8 step for you.
import base64
def basic_auth(user, password):
raw = f"{user}:{password}".encode("utf-8")
return "Basic " + base64.b64encode(raw).decode("ascii")
print(basic_auth("Aladdin", "open sesame"))
def parse_basic(header):
scheme, _, token = header.partition(" ")
if scheme.lower() != "basic":
return None
user, sep, password = base64.b64decode(token).decode("utf-8").partition(":")
return (user, password) if sep else None
print(parse_basic("Basic dGVzdDoxMjPCow=="))
# With the requests library the header is built for you:
# requests.get("https://api.example.com/", auth=("user", "pass")) <?php
$header = 'Basic ' . base64_encode('Aladdin:open sesame');
echo $header, PHP_EOL;
// Sending it with cURL
$ch = curl_init('https://api.example.com/');
curl_setopt($ch, CURLOPT_USERPWD, 'user:pass'); // cURL builds the header
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
// $body = curl_exec($ch);
// Reading it on the server
$user = $_SERVER['PHP_AUTH_USER'] ?? null;
$pass = $_SERVER['PHP_AUTH_PW'] ?? null;
if ($user === null || !hash_equals('expected-secret', (string) $pass)) {
header('WWW-Authenticate: Basic realm="Staging", charset="UTF-8"');
http_response_code(401);
exit('Unauthorized');
} PHP_AUTH_USER is empty under PHP-FPM behind some Apache and nginx setups. Pass the Authorization header through (for example CGIPassAuth On) or parse HTTP_AUTHORIZATION yourself.
RFC 7617 defines the scheme and lets a server ask for UTF-8 by adding charset="UTF-8" to its WWW-Authenticate challenge. Clients that ignore it may still send Latin-1, which is why the decoder falls back to Latin-1 and says so when the bytes are not valid UTF-8.
This tool follows the specifications below. Read them when you need the exact normative wording, for example to settle an edge case between two implementations.
Join username and password with a colon, encode that as UTF-8, Base64 the bytes, and send Authorization: Basic followed by the result. Aladdin and open sesame become QWxhZGRpbjpvcGVuIHNlc2FtZQ==.
Only over HTTPS. The header is Base64, which anyone can decode, so over plain HTTP the password is effectively sent in clear text on every request.
Switch Mode to Decode and paste the header, with or without the Authorization: and Basic parts. The username and password are split at the first colon.
No. The server splits at the first colon, so a colon in the username ends up in the password. The password itself may contain colons.
No. The header and examples are built with JavaScript in this tab and the page makes no request with them. As with any credential, avoid typing a production password on a shared machine.
The header is plain Base64, so the Base64 encoder shows the raw step. Bearer tokens are usually JWTs, which the JWT decoder reads, and if you need a strong password to put in the header, use the password generator.