The grammar
RFC 9110 defines the field as an authentication scheme followed by credentials, separated by a single space:
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxIn0.dBjftJeZ4CVP…
^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
scheme credentials
The scheme name is a token, and it is case-insensitive — Bearer,
bearer and BEARER are the same scheme, though matching them all is the server's
job and not every server does it. The credentials that follow are opaque to HTTP itself: the protocol
defines the envelope and each scheme defines what may go inside it.
There are exactly two shapes credentials may take, and knowing which shape a scheme uses explains most of the syntax you will ever meet.
Form 1: token68
A single opaque blob, from a deliberately restricted alphabet: letters, digits, and
- . _ ~ + /, with
= permitted only as trailing padding. The name refers to those 68 allowed characters. The
set exists because it covers base64, base64url and hex without needing any quoting or escaping, so a
credential can be dropped into the header untouched.
Authorization: Basic YWxhZGRpbjpvcGVuc2VzYW1l Authorization: Bearer 6f8d9c2b-1e4a-4f77-9a03-8f1e2c7b4d55
Notice what the alphabet excludes: spaces, commas, quotes and newlines. A token containing any of those is
not a valid token68, which is why a credential pasted from a wrapped terminal window — carrying an
embedded newline — produces a header the server cannot parse and a 401 that says nothing
useful.
Form 2: a list of auth-params
Alternatively, credentials may be a comma-separated list of name=value pairs, where values
are tokens or quoted strings. Schemes that need to carry several distinct fields use this form:
Authorization: Digest username="ada", realm="api@example.com", nonce="dcd98b7102dd2f0e", uri="/v1/orders", qop=auth, nc=00000001, cnonce="0a4f113b", response="6629fae49393a05397450978507c4ef1", opaque="5ccc069c403ebaf9f0171e9517f40e41"
Parameter names are case-insensitive; values in quotes may contain characters a bare token may not. A header in this form may legally be folded across lines in the way shown above, though modern practice is to send it on one line.
One header, one scheme. If you need to present credentials to both a proxy and an origin server, that is
what the separate Proxy-Authorization header is for — see below. Sending two
Authorization headers is undefined behaviour, and what a server does with it ranges from
taking the first, to taking the last, to joining them with a comma and failing to parse the result.
The scheme registry
IANA maintains the list of registered HTTP authentication schemes. Most developers meet two of them; the
rest are worth recognising when they appear in a WWW-Authenticate challenge you did not
expect.
| Scheme | Defined by | Credential form | What it carries |
|---|---|---|---|
Basic | RFC 7617 | token68 | Base64 of user:password — encoded, not encrypted |
Bearer | RFC 6750 | token68 | An OAuth 2.0 access token; possession alone authorises |
Digest | RFC 7616 | auth-params | A hash over a server nonce, so the password never crosses the wire |
Negotiate | RFC 4559 | token68 | A base64 SPNEGO token wrapping Kerberos or NTLM |
HOBA | RFC 7486 | token68 | A digital signature bound to the origin, replacing passwords entirely |
Mutual | RFC 8120 | auth-params | Password-based mutual authentication where the server also proves itself |
SCRAM-SHA-256 | RFC 7804 | auth-params | A salted challenge–response exchange over several round trips |
vapid | RFC 8292 | auth-params | Identifies an application server to a Web Push service |
Basic
The credential is base64(username + ":" + password). Base64 is an encoding, not a cipher —
anyone who sees the header can recover the password instantly, which you can demonstrate to a sceptical
colleague in one step with the Base64 decoder. Basic is therefore acceptable
only over TLS. The colon is a true delimiter, so a username containing one is unrepresentable; passwords
may contain colons freely, since only the first separates the two fields. RFC 7617 added a
charset="UTF-8" parameter to the challenge, which is how a server signals it will handle
non-ASCII characters predictably.
Bearer
The dominant scheme in modern APIs, and the simplest possible model: whoever holds the token gets the
access, exactly like a train ticket. The token may be a JWT the server
validates by signature, or an opaque string it looks up in a store — the header cannot tell you which, and
does not need to. RFC 6750 also defines two additional transports, a form field and a query parameter,
both of which should be avoided because URLs end up in access logs, browser history and
Referer headers. The bearer token guide covers lifetime,
storage and revocation.
Digest
Designed for a world without ubiquitous TLS. The server issues a nonce in its challenge and the client
returns a hash over the nonce, the credentials, the method and the URI, so the password itself never
travels. RFC 7616 modernised it with SHA-256 and added the userhash parameter to hide the
username too. It survives in embedded devices, routers and some enterprise systems, but it is rarely
chosen for a new API: TLS solves the eavesdropping problem more comprehensively, and Digest requires the
server to hold a password-equivalent value, which conflicts with modern password storage.
Negotiate, HOBA, Mutual and SCRAM
Negotiate is what single-sign-on inside a Windows domain looks like on the wire: a base64
SPNEGO blob wrapping Kerberos or NTLM, exchanged over several round trips and bound to the connection
rather than to the request. HOBA replaces the password with a client-held key pair and an
origin-bound signature. Mutual lets both sides prove knowledge of a password without either
transmitting it, defeating phishing. SCRAM-SHA-256 brings the salted challenge–response
mechanism familiar from databases and mail servers to HTTP. All four are genuinely registered and all four
are rare in public APIs; recognise them, and reach for the relevant RFC only if a challenge names one.
Schemes that are not registered but are everywhere
Nothing stops a service defining its own scheme name, and several large ones have. These are not standards, but they are common enough that you will meet them:
AWS4-HMAC-SHA256— Signature Version 4, and the best real-world example of the auth-param form. The credential is a structured list:Credential=AKIA…/20260720/eu-west-1/s3/aws4_request, SignedHeaders=host;x-amz-date, Signature=fe5f80f7…. The signature covers the method, path, query, a named set of headers and a hash of the body, so the request cannot be altered in transit — a very different security property from a bearer token, and the reason a mis-sorted header list breaks the whole thing.Bot— used by Discord for bot tokens, where sending the same token asBearerfails.TokenandApiKey— conventions in various APIs for a long-lived key.
The lesson is procedural rather than technical: read the specific API's documentation for the exact scheme string. A server comparing against a scheme name it did not expect rejects a perfectly valid credential, and the error message almost never mentions the scheme. If you are checking a key against a provider right now, the API key tester pre-fills the correct shape for several common services.
The challenge–response flow
HTTP authentication is not something a client is supposed to guess at. The protocol defines a negotiation: the client asks, the server refuses and says what it would accept, the client asks again with credentials.
GET /v1/orders HTTP/1.1 Host: api.example.com → 401 Unauthorized WWW-Authenticate: Bearer realm="api", scope="orders.read" GET /v1/orders HTTP/1.1 Host: api.example.com Authorization: Bearer eyJhbGciOiJIUzI1NiJ9… → 200 OK
A 401 must carry a WWW-Authenticate header.
This is a requirement of the specification and it is very widely ignored. The header is what makes the
status actionable: without it the client knows only that something was wrong, and with it the client knows
which scheme to use and which protection space it belongs to.
You can watch the whole exchange in about twenty seconds: send an unauthenticated GET from the
free API tester to any protected endpoint and read the WWW-Authenticate header
on the 401 that comes back. Whatever it names is the scheme the server will accept, which is
more reliable than the documentation on a surprising number of APIs.
A server may offer several challenges, either as repeated header fields or as one
comma-separated list, in order of preference. The client picks the strongest scheme it supports. This is
also the mechanism behind the browser password prompt: a plain Basic challenge is what makes
a browser show its native login dialog, which is precisely why APIs consumed by JavaScript often avoid the
Basic scheme name — nobody wants a native dialog appearing over their application.
Challenge parameters
| Parameter | Applies to | Meaning |
|---|---|---|
realm | Any scheme | Names the protection space, so a client knows which credential applies. Shown to users in browser prompts |
charset | Basic | Only UTF-8 is defined; signals how non-ASCII credentials are encoded |
error | Bearer | invalid_request, invalid_token or insufficient_scope |
error_description | Bearer | Human-readable detail — for developers, never for parsing |
scope | Bearer | The scopes required for this resource |
nonce, qop, algorithm, opaque | Digest | Inputs the client must fold into its response hash |
The Bearer error parameters are the most useful and most neglected diagnostic in API
authentication. error="invalid_token" tells a client its token is expired or malformed and it
should refresh; error="insufficient_scope" with a scope parameter tells it the
token is fine but lacks a permission, and names the missing one. RFC 6750 pairs the latter with
403 rather than 401, which is the clearest expression
of the distinction: 401 means the server does not know who you are, 403 means it
does and is refusing anyway. Retrying a 403 with the same credential is pointless by
definition.
One more header closes the loop. Authentication-Info lets a server return authentication data
alongside a successful response — Digest uses it to prove it knew the password too, and to hand over the
next nonce so the following request avoids an extra round trip.
Proxy-Authorization: the same grammar, a different audience
Everything above concerns the origin server. Intermediaries have a parallel set of headers, with identical syntax and a completely separate purpose.
| Origin server | Proxy | |
|---|---|---|
| Challenge status | 401 Unauthorized | 407 Proxy Authentication Required |
| Challenge header | WWW-Authenticate | Proxy-Authenticate |
| Credential header | Authorization | Proxy-Authorization |
| Scope | End to end — forwarded onwards | Hop by hop — consumed and removed |
The last row is the substantive difference. Authorization is an end-to-end field: an
intermediary passes it along untouched, because it is addressed to the origin.
Proxy-Authorization is hop-by-hop: the proxy it is addressed to consumes it and does
not forward it, so the origin never sees your proxy password. A single request can carry both
headers with entirely unrelated credentials — corporate proxy on one, API token on the other — and that is
exactly the intended design.
Practically, a 407 in your logs is not your API rejecting you. It is a network intermediary
between you and it, which is why the fix lives in proxy configuration rather than in your token handling.
Where the header gets removed without telling you
Cross-origin redirects
Send an authenticated request, receive a 302 to another host, and
the follow-up request will very likely go out without your Authorization
header. Browsers strip it. curl strips it unless you explicitly pass
--location-trusted. Most mainstream HTTP libraries strip it.
This is correct behaviour and worth understanding rather than working around. A redirect is an instruction from one server naming a URL of its choosing. If credentials were forwarded automatically, any server you authenticate to could redirect you to a host it controls and harvest your token — turning an open redirect from a nuisance into full credential disclosure. Because the client cannot know whether the new host is trustworthy, it assumes it is not.
The symptom is distinctive: a request that returns 401 even though the identical credential
works elsewhere, with a redirect somewhere in the chain. Common triggers are an http URL
upgrading to https, an apex domain redirecting to www, and a missing trailing
slash. The fix is to request the final URL directly, not to relax the rule. If a redirect
genuinely must carry credentials, the destination should be a host you control and you should be re-adding
the header knowingly.
Cross-origin requests from a browser
In a browser, setting Authorization on a cross-origin fetch takes the request out
of the CORS simple category and forces an OPTIONS preflight — and the preflight itself carries
no credentials at all. The server must both permit the header by name in
Access-Control-Allow-Headers and answer the OPTIONS without demanding
authentication, or the real request is never sent. CORS explained covers the
full sequence.
Shared caches
A request carrying Authorization is, by default, not stored by shared caches — RFC 9111 makes
that the rule, on the sound assumption that an authenticated response is about one user and must not be
served to another. A response may opt back in with explicit directives such as public or
s-maxage, and doing so without thinking is a good way to serve one customer's data to the
next. Private caches in the user's own browser are not affected.
Logs and proxies
Some intermediaries strip or rewrite headers they do not recognise, and some log them. A credential in a
URL is logged everywhere, which is the main argument for keeping it in a header — though a header is only
as private as your logging configuration, so redact Authorization explicitly rather than
hoping. It is also worth knowing that an oversized header — a very large JWT plus a long cookie — can be
refused with 431 Request Header Fields Too Large by a server whose
buffer it exceeds.
Diagnosing a malformed header
Before assuming the credential is wrong, rule out the envelope. These are ordered roughly by how often they turn out to be the answer.
| Header sent | Problem | Correct form |
|---|---|---|
Authorization: eyJhbGci… | No scheme at all | Authorization: Bearer eyJhbGci… |
Authorization: Bearer Bearer eyJ… | Scheme included in the stored token as well | Store the token alone; add the scheme when building the header |
Authorization: Bearer eyJ… | Two spaces — the credential now begins with a space | Exactly one space |
Authorization: Bearer eyJ…\n | A newline pasted in from a wrapped terminal | Trim the value before setting the header |
Authorization: Basic ada:hunter2 | Base64 encoding skipped | Base64 the user:password pair first |
Authorization: Basic YWRhOmh1bnRlcjI=\n… | Base64 tool wrapped its output across lines | Encode without line wrapping |
Authorization: Bot <token> sent as Bearer | Wrong scheme for that API | Use the exact scheme string the provider documents |
| Header set but never arrives | Stripped by a cross-origin redirect, or the client dropped it on a retry | Request the final URL directly; check the redirect chain |
To settle it definitively, send the request from the API tester to an echo endpoint such as
https://httpbin.org/headers and read back exactly what arrived — scheme, spacing and all. That
one comparison separates “my header is malformed” from “my credential is rejected”, which are different
problems with no overlap in their fixes. For the shape of the credential inside the header, continue to
bearer tokens; for choosing between schemes in the first place, the
API authentication guide compares all of them, and
OAuth vs JWT untangles the two things most often confused with each
other. Neighbouring header behaviour is covered in the
HTTP headers reference and the
Content-Type guide.
Frequently asked questions
What is the correct syntax of the Authorization header?
A case-insensitive scheme name, one space, then the credentials. RFC 9110 allows two credential shapes:
a single token68 blob, used by Basic and Bearer, or a
comma-separated list of name=value parameters, used by Digest and AWS Signature
Version 4. The scheme is never optional.
Why did my Authorization header disappear after a redirect?
The client dropped it on purpose. Browsers, curl and most libraries strip Authorization
when a redirect crosses to a different origin, because forwarding it would hand your credential to
whatever host the first server named. Request the final URL directly rather than relaxing the
rule.
What is the difference between 401 and 403?
401 means unauthenticated — the server is challenging you, which
is why it must send WWW-Authenticate. 403 means
unauthorised — it knows who you are and is refusing anyway, so the same credential will not help on a
retry.
What does the realm parameter in WWW-Authenticate mean?
It names a protection space on that origin so a client knows which stored credential applies; paths sharing a realm accept the same credentials. It is the one parameter defined for every scheme, and because browsers display the string in their prompt it should be short and meaningful.
How is Proxy-Authorization different from Authorization?
It authenticates you to an intermediary rather than the origin. Same grammar, but the proxy consumes
and removes it instead of forwarding it, and a proxy demanding credentials answers 407 with
Proxy-Authenticate. One request may carry both headers with different
credentials.
Can I send an API key in the Authorization header?
Yes, though the scheme varies: some APIs define Token or ApiKey, some ask you
to send the key as a Bearer token, and some prefer a custom header such as
X-API-Key. Follow the documentation exactly — a server matching an unexpected scheme name
rejects a valid key.