Reference guide · http-status · Published 2026-08-15 · 4 min read
HTTP 401 unauthorized explained
HTTP 401 unauthorized explained: the status means authentication failed, not authorization. How 401 differs from 403 and how clients, servers and security tools behave.
- ·What 401 means
- ·401 vs 403
- ·Authorization header
What a 401 means
A 401 Unauthorized is the server saying: "I need you to identify yourself, and you have not, or the credentials you sent are wrong." The client can answer by supplying the right evidence for that request, and the same URL is then serveable. It is not the same as forbidden: a 403 means you are known to the server and still not allowed to read the file.
In HTTP, the header the server uses to offer the challenge is WWW-Authenticate, and the client answers in Authorization. Here is the pair:
| Header | Travels route | Example |
|---|---|---|
WWW-Authenticate | Server to client, carries the challenge | WWW-Authenticate: Basic realm="admin" |
Authorization | Client to server, carries the evidence | Authorization: Bearer abc.def.ghi |
401 versus 403 in one view
| 401 Unauthorized | 403 Forbidden | |
|---|---|---|
| Meaning | "I do not know who you are yet" | "I know who you are, you cannot have it" |
| Real user cause | Bad or missing token, login needed | No permission, rule or block |
| Fix direction | Retry with correct credentials | Change the rule, permission or block |
It is easy to swap them when debugging, because a browser shows a similar error. File the status from your log, not from memory: many security gateways return 403 for odd traffic even when there was no identity at all, so the code in the log is the source of truth about your application.
How the browser and API behave
- Browsers on HTML pages: when they get 401 Basic against a resource in an iframe or that was opened directly, they show their built-in credential dialog and cache the reply in the browser so you may not be retried.
- Frameworks that treat every 401 as a login redirect can loop; keep the redirect to the OAuth flow, not a random sign-in page.
- 401 to a form login that never sends the
WWW-Authenticatechallenge is a common integration bug: the server wants a cookie, the client looks for a header, and the two never agree.
How to verify with curl
# 1. No credentials: expect 401 and a WWW-Authenticate header
curl -I https://api.example.com/private/
# HTTP/1.1 401 Unauthorized
# WWW-Authenticate: Basic realm="api"
# 2. Correct basic token
curl -u username:password -I https://api.example.com/private/
# HTTP/1.1 200 OK
# 3. Expired or wrong token (Bearer style)
curl -H "Authorization: Bearer invalid" https://api.example.com/private/
# HTTP/1.1 401
Prevent and diagnose common 401 pile-ups
- The framework treats the auth scheme token case-insensitively, but many APIs require the exact casing, typically
Bearer. If a 401 persists with a valid token, check the scheme casing. - The server answers 200 for the login page (200 with a login form) and one 401 only for actual failed attempts; check the failed flow with the wrong password.
- Clear cached credentials: a browser-stored 401 reply or an old saved credential can keep a 401 burning even after you fix the server-side.
- Check that the application server does not return a stale
WWW-Authenticate. A Bearer scheme you no longer use is the classic villain.
Prevention
- Send the same methods for every protected path: verify that the 401 includes
WWW-Authenticatefor Basic and the scheme for Bearer. - Verify that the scheme name is the one your clients expect, and only raise the challenge when the route is truly a protected resource.
- Make the error body explain: "token expired" vs "bad credentials" in the JSON body, which beats guessing from the code alone.
- In a session, return 401 to a valid but wrong session ID, not a generic 403.
When to use which
If the request lacks any authentication evidence, return 401. If authentication succeeded but the account is not allowed to see the resource, return 403. Sending 403 for everything hides the cause and costs debugging time.