Quick answer
HTTP 401 Unauthorized means the request lacks valid authentication credentials. The client must authenticate — log in or send a valid token — before the request will be accepted.
What HTTP 401 means
401 Unauthorized is, despite its name, about authentication: the server does not know who you are. No credentials were sent, or the ones sent are invalid or expired. The response should include a WWW-Authenticate header describing how to authenticate.
Common causes
- No
Authorizationheader was sent - The token or API key is expired
- The token is malformed or uses the wrong scheme
- Wrong username/password on a basic-auth endpoint
Example JSON error response
{
"error": "Unauthorized",
"message": "Missing or invalid authentication token",
"status": 401
}
Raw HTTP response
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer
Content-Type: application/json
How to troubleshoot HTTP 401
- ✓ Confirm an
Authorizationheader is being sent - ✓ Check the token has not expired — decode the JWT to inspect its
expclaim - ✓ Verify the scheme is correct (e.g.
Bearer <token>) - ✓ Confirm you are using the right credentials for this environment
- ✓ Re-authenticate to obtain a fresh token
401 vs 403 — what's the difference?
401 Unauthorized means "who are you?" — you are not authenticated, so log in or send a valid token. 403 Forbidden means "I know who you are, but you can't do this" — you are authenticated but lack permission. If sending valid credentials fixes it, it was a 401; if you have valid credentials but still can't access the resource, it's a 403.
Handling 401 in client code: the token-refresh flow
The standard pattern is an interceptor: when a request comes back 401, try refreshing the access token once, then replay the original request. If the refresh itself 401s, the session is genuinely over — send the user to log in:
async function apiFetch(url, opts = {}) {
let res = await fetch(url, withAuth(opts));
if (res.status === 401) {
const refreshed = await tryRefreshToken(); // exchange refresh token
if (!refreshed) { redirectToLogin(); return; }
res = await fetch(url, withAuth(opts)); // replay once
}
return res;
}
Guard against infinite loops — only retry once. To see why a token is rejected, decode the JWT and check its exp claim; for whether that's safe to do online, see is it safe to decode a JWT online?
The WWW-Authenticate header
A spec-compliant 401 includes a WWW-Authenticate header telling the client how to authenticate — e.g. WWW-Authenticate: Bearer for token auth or Basic realm="..." for basic auth. If your API omits it, clients (and browsers) can't tell what scheme to use; include it so the 401 is actionable.
Frequently Asked Questions
What is the difference between 401 and 403?
401 Unauthorized means you are not authenticated — no valid credentials were supplied. 403 Forbidden means you are authenticated but lack permission for this resource. 401 is "who are you?", 403 is "you can't do this."
How do I fix a 401 Unauthorized?
Make sure you send a valid Authorization header, that the token has not expired, and that the scheme is correct (usually Bearer). Decoding the token lets you check its expiry. If expired, re-authenticate to get a new one.
Why do I get a 401 with a token that worked yesterday?
The token has most likely expired. JWTs carry an exp claim — decode the token to check it. Tokens are short-lived by design; obtain a fresh one by re-authenticating or refreshing.
Should I retry a 401 automatically?
Retry once, and only after refreshing the token. The pattern is: on 401, exchange your refresh token for a new access token, then replay the original request a single time. If the refresh also fails, the session is over — send the user to log in. Never retry in a loop, or you'll hammer the auth endpoint.
Does a 401 response need a WWW-Authenticate header?
Per the HTTP spec, yes — a 401 should include a WWW-Authenticate header indicating the auth scheme (e.g. Bearer). Many JSON APIs omit it, which still works for custom clients but is technically non-compliant and leaves browsers unable to prompt for credentials.
Working with a JSON API response?
Format and inspect any response in your browser — nothing is uploaded.