WWW-Authenticate header: Basic auth in nginx, Bearer, and why the login prompt doesn't appear (tested)
A 401 response should say how to authenticate in WWW-Authenticate. We protected a folder with nginx auth_basic and logged what curl and Chrome did with each answer.
1. What nginx auth_basic answered
| Request | Status | Response header / error log |
|---|---|---|
| No credentials | 401 | WWW-Authenticate: Basic realm="Staging area" |
curl -u alice:s3cret | 200 | page served |
| Wrong password (or wrong case) | 401 | log: user "alice": password mismatch |
| Unknown user | 401 | log: user "bob" was not found in "/etc/nginx/htpasswd" |
Authorization: Bearer abc | 401 | Basic challenge again: nginx only understands Basic |
| Password file missing | 403 | log: open() "/etc/nginx/nope" failed |
location /staging/ {
auth_basic "Staging area"; # becomes realm="Staging area"
auth_basic_user_file /etc/nginx/htpasswd;
}
location /staging/health { auth_basic off; } # public exception
Log lines tell you which case you're in; the client always sees the same 401.
2. The trap: return skips auth_basic
With return 200 "..."; in the protected location, every request got 200 without credentials, no 401 at all. return runs in nginx's rewrite phase, before the access phase where auth_basic checks the password. Protect locations that serve files or proxy_pass; don't test your setup with a return stub.
3. Basic vs Bearer, and when the browser asks
WWW-Authenticate: Basic realm="...": Chrome sent the stored user/password only after this challenge, then reused it for a same-originfetch()(200).WWW-Authenticate: Bearer realm="api", error="invalid_token": Chrome sent nothing and kept the 401. Browsers never prompt for tokens; your JavaScript addsAuthorization: Bearer ...itself (Bearer token header, explained).- 401 with no WWW-Authenticate: no credentials sent, no prompt. That's the usual reason a "login prompt doesn't appear": an API returns 401 for a missing token and the browser has nothing to answer.
Basic sends the password base64-encoded, not encrypted: use it only over HTTPS (HSTS in nginx). Inspect what a URL sends with the free HTTP headers checker.
Frequently asked questions
What does WWW-Authenticate: Basic realm mean?
The server wants a username and password (HTTP Basic). The realm is the label from nginx's auth_basic, e.g. auth_basic "Staging area" sent realm="Staging area" in our test.
Why is nginx auth_basic not working?
In our test a location using return 200 served every request without asking for a password, because return runs before auth_basic. Also check the error log: a missing password file gave 403, not 401.
Why doesn't the browser show a login prompt on 401?
The response needs WWW-Authenticate: Basic. With no WWW-Authenticate header, or a Bearer challenge, Chrome sent no credentials in our test.
Can nginx auth_basic check a Bearer token?
No. A Bearer Authorization header got the Basic 401 challenge back; token checks need your app (or nginx auth_request, per the nginx docs).
What's the difference between 401 and 403 here?
401 + WWW-Authenticate means 'send credentials'. nginx answered 403 when the password file couldn't be opened.
Written with AI assistance. Measured on 2026-10-01 with a throwaway nginx:alpine container (auth_basic + auth_basic_user_file, removed afterwards), curl, and Chromium 153 (full build) with Playwright httpCredentials against nginx and a small Node server that answered 401 with no WWW-Authenticate or with a Bearer challenge. Password hashing advice (htpasswd -B / bcrypt) is from the nginx docs, not this test; the test file used a {PLAIN} entry. PlainHeaders is our extension; the other methods are free.