Skip to content

401 vs 403

401Unauthorized — who are you?403Forbidden — not allowed

Short answer

Return 401 when the request has not proved who it is — no credentials, or invalid or expired ones — because retrying with good credentials could succeed. Return 403 when identity is established and this user still may not do this, because retrying changes nothing. The practical test is whether logging in again could fix it.

Side by side

 401403
Actually meansUnauthenticatedUnauthorised
Server knows who you areNoYes
Would valid credentials helpYesNo
Requires WWW-Authenticate headerYes, per the specNo
Typical causeMissing, invalid or expired tokenValid user without the required permission
Sensible client responsePrompt for login, or refresh the tokenShow a 'no access' message
Cacheable by defaultNoYes

When to use each

Use 401 when…

No Authorization header, a malformed or expired token, a bad signature, or a session that has timed out. Anything where authenticating properly would let the request through.

Use 403 when…

A logged-in user requesting an admin-only endpoint, a tenant reaching for another tenant's record, or an action blocked by policy — IP restrictions, a suspended account, or file permissions on the server.

The names are the problem

401 is called Unauthorized, but it means unauthenticated — the request has not established who is making it. 403 is called Forbidden and is the one that actually concerns authorisation.

This naming was acknowledged as misleading long ago and kept for compatibility. The HTTP specification itself notes that 401 is about authentication despite the name.

The reliable way to remember it is to ignore the words and ask a question: could logging in with the right account make this request succeed? If yes, it is a 401. If the user is already correctly identified and simply is not permitted, it is a 403.

The WWW-Authenticate requirement

A 401 response is required by the specification to include a WWW-Authenticate header naming the authentication scheme the client should use — Bearer, Basic, or something else.

Most APIs omit it, and most clients do not care, so this is widely ignored in practice. It matters for standards-compliant clients and for browser-native authentication flows, where the header is what triggers the credentials prompt.

If you return 401 from an API, including WWW-Authenticate: Bearer costs nothing and makes the response correct.

When 404 is the better answer

There is a case where neither code is right. Returning 403 for a resource that exists but is not yours confirms that it exists — which leaks information. An attacker enumerating IDs learns exactly which records are real by watching for 403 rather than 404.

For anything sensitive, returning 404 for both 'does not exist' and 'exists but is not yours' reveals nothing. GitHub does this with private repositories: you get a 404, not a 403.

The trade-off is debuggability, since a legitimate user with a genuine permission problem sees a confusing 404. Use 404 where enumeration is a real risk, and 403 where clarity matters more.

Frequently asked questions

Which should I return for an expired token?
401. The token was valid and no longer is, so re-authenticating or refreshing would let the request through — which is precisely what 401 signals. Returning 403 tells the client not to bother retrying, so it never refreshes and the user is stuck.
What about an API key that has run out of quota?
429 Too Many Requests, with a Retry-After header, is the accurate code for rate limiting. 403 is a reasonable second choice for a hard quota that will not reset, but 401 is wrong because the credentials are valid.
Should a 403 explain why access was denied?
It depends on sensitivity. A helpful message such as 'this action requires the admin role' greatly improves the experience for legitimate users. For anything security-sensitive, a detailed reason gives an attacker a map of your permission model, so keep it vague.
Why am I getting a 403 on a static file?
Usually file permissions or an ownership problem rather than application logic — the web server user cannot read the file, or cannot traverse a parent directory. Check that files are readable and that every directory in the path has the execute bit set.

Try the HTTP Status Code Lookup

Look up any HTTP status code by number or search by name to see what it means, whether it is cacheable and what usually causes it.

Open tool

More comparisons