Response header
< WWW-Authenticate: Bearer realm="api", error="invalid_token"WWW-Authenticate
Learn how the WWW-Authenticate header specifies authentication methods required to access protected resources. Understand Basic, Bearer, and Digest schemes.
- Direction
- Response
- Category
- Authentication
- Spec
- RFC 9110 §11.6.1
On this page
TL;DR:
WWW-Authenticateis the response header that tells a client how to log in, for exampleWWW-Authenticate: Basic realm="Admin"orBearer realm="API". Every401 Unauthorizedmust include at least one challenge; the client answers with anAuthorizationheader in that scheme. For Bearer: no token gets a bare challenge, a bad or expired token gets 401 witherror="invalid_token", and a token lacking scope gets 403 witherror="insufficient_scope".
The header describes how to authenticate for the requested resource. It’s mandatory on a 401, and servers may send challenges on other responses too. RFC 9110 §11.6.1 sets the rule.
How It Works
Here’s a server challenging a request that arrived without credentials (examples on this page are trimmed):
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Basic realm="Admin", charset="UTF-8"
Content-Length: 0
The client can retry with a matching Authorization header. Whether a browser shows a login prompt or retries on its own depends on the client and the kind of request.
Authentication Schemes
Basic sends username:password base64-encoded. Base64 is an encoding, not encryption, so only use Basic over TLS. Bearer grants access to whoever holds the token; the token can be any string and needn’t be a JWT. Digest has the client compute a response from the server’s nonce and other parameters. Its security depends on the algorithm, so check which one you’re using.
See RFC 7617, RFC 6750, and RFC 7616 for these schemes. RFC 6750 defines OAuth 2.0 Bearer authentication.
A realm names a protection space: a set of resources that share credentials. Basic challenges must include one; for Bearer it’s optional. It’s only a label, with no permissions or login URL attached. RFC 9110 §11.5 defines protection spaces.
Multiple Authentication Options
A response can offer several challenges. This one sends two on separate header lines:
WWW-Authenticate: Basic realm="Admin"
WWW-Authenticate: Bearer realm="API"
Clients pick a scheme they support, and nothing says it has to be the first one listed. If you parse this header yourself, be careful with commas: they separate both parameters and challenges, and can appear inside quoted values. A plain split(',') gets all of that wrong.
OAuth 2.0 Error Parameters
RFC 6750 §3.1 defines three Bearer error codes and the status that normally goes with each:
invalid_request: malformed authentication request; normally 400.invalid_token: expired, revoked, or otherwise invalid token; normally 401.insufficient_scope: valid authentication lacking required scope; normally 403, withscopeoptionally naming what is needed.
When the request carried no credentials at all, leave out the error code and error details. A bare WWW-Authenticate: Bearer realm="API" is all the client needs.
Real-World Examples
The response to an expired token:
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="API", error="invalid_token"
Content-Length: 0
The response to a valid token without the needed permission:
HTTP/1.1 403 Forbidden
WWW-Authenticate: Bearer realm="API", error="insufficient_scope", scope="reports:read"
Content-Length: 0
reports:read is a made-up application scope; scope names are whatever your API defines.
Server Implementation
nginx sends a Basic challenge through its auth_basic module. First create the password file with Apache’s htpasswd utility:
htpasswd -c /etc/nginx/admin.htpasswd alice
-c creates the file, wiping it if it exists, so leave it off when you add more users. Then add this to an existing TLS server block:
location /admin/ {
auth_basic "Admin";
auth_basic_user_file /etc/nginx/admin.htpasswd;
proxy_pass http://127.0.0.1:3000;
}
Check the challenge, then log in (curl prompts for the password):
curl -i https://example.com/admin/
curl --user alice -i https://example.com/admin/
Use your own hostname, and make sure the nginx worker can read the password file. Apache documents htpasswd and the password formats it supports.
Express Basic authentication
Let a middleware decode the credentials instead of writing your own split(':'). Install express and express-basic-auth, save this as app.mjs, and set a test password in ADMIN_PASSWORD:
import express from 'express'
import basicAuth from 'express-basic-auth'
const password = process.env.ADMIN_PASSWORD
if (!password) throw new Error('Set ADMIN_PASSWORD')
const app = express()
app.use('/admin', basicAuth({
users: { alice: password },
challenge: true,
realm: 'Admin'
}))
app.get('/admin', (req, res) => res.json({ status: 'authenticated' }))
app.listen(3000, '127.0.0.1')
The package’s challenge option adds WWW-Authenticate to rejected requests; without it you get a 401 with no challenge. The in-memory account is a demo; a real deployment checks your user store and runs behind TLS. Test both paths:
curl -i http://127.0.0.1:3000/admin
curl --user alice -i http://127.0.0.1:3000/admin
The second command prompts for the password. Register the auth middleware before the protected route, so it runs first. A JSON body that says “unauthorized” isn’t a challenge; the header is.
Security Considerations
If you decode Basic credentials yourself, split at the first colon only: usernames can’t contain a colon, but passwords can. Scheme names are case-insensitive, so basic is as valid as Basic. Parsers that match an exact Basic prefix or split parameters on commas break on valid input; use a tested library.
Getting WWW-Authenticate right
Look at the actual response on the wire, not just the status your app sets. Every 401 needs the challenge header, whatever the body says. Check that it survives your reverse proxy, too; RFC 9110 forbids a proxy from modifying WWW-Authenticate when it forwards a response.
Related Headers
Frequently asked questions
What is WWW-Authenticate?
WWW-Authenticate is sent with 401 responses to tell clients how to authenticate. It specifies the authentication scheme (Basic, Bearer, Digest) and parameters like realm.
What authentication schemes are common?
Basic, Bearer, and Digest are defined HTTP authentication schemes. Bearer tokens need not be JWTs, and supported schemes depend on the server and client.
What is the realm parameter?
Realm names a protection space, so clients know which credentials go with which part of a site. Basic challenges must include it; Bearer challenges may. It is only a label and grants no permissions.
Can I use multiple authentication schemes?
Yes. A response can carry several challenges, in one header or on separate WWW-Authenticate lines. The client picks one it supports, and it need not pick the first.
Sources
Related
Authentication-Info Header
Learn how Authentication-Info provides additional authentication data in responses to successful requests. Covers digest authentication and session info.
Proxy-Authenticate Header
Learn how the Proxy-Authenticate header challenges clients for credentials when accessing resources through a proxy. Understand proxy authentication schemes.
Set-Cookie Header: Attributes, Examples and Browser Rules
Set-Cookie syntax and every attribute: Expires, Max-Age, Domain, Path, Secure, HttpOnly, SameSite, Partitioned, __Host- prefixes, with Express and Django.
Authorization Header: Bearer, Basic and 401 Fixes
Authorization header carries credentials as scheme plus token. Bearer and Basic formats, WWW-Authenticate, why proxies strip it, redirects, CORS and storage.