Developers

401 Unauthorized vs 403 Forbidden

Two codes, one question: would logging in (again) change the answer? If yes, send 401. If no, send 403. Here is the spec behind that rule and the edge cases that trip people up.

401 and 403 are the most commonly swapped pair of status codes on the web, partly because their names are misleading. "Unauthorized" sounds like a permissions problem, but in HTTP it is about authentication: who you are. "Forbidden" is about authorization: what you are allowed to do. Once you separate those two ideas, choosing the code is easy.

What the spec actually says#

RFC 9110 §15.5.2 defines 401 as a request that "has not been applied because it lacks valid authentication credentials for the target resource." It adds a hard requirement: the server "MUST send a WWW-Authenticate header field … containing at least one challenge." A 401 is therefore an invitation: here is how to authenticate, please try again.

RFC 9110 §15.5.4 defines 403 as the server having "understood the request but refuses to fulfill it." If credentials were provided, "the server considers them insufficient to grant access," and the client "SHOULD NOT automatically repeat the request with the same credentials." It also notes that a request "might be forbidden for reasons unrelated to the credentials," such as an IP block.

MDN sums it up neatly: for 403, "authenticating or re-authenticating makes no difference."

401 Unauthorized403 Forbidden
Question it answersWho are you?You are not allowed to do this
ConceptAuthenticationAuthorization (or a policy block)
Required headerWWW-Authenticate (MUST)None
Will logging in help?YesNo
Client shouldObtain or refresh credentials, retryStop, or try different credentials
Typical triggerMissing, expired, revoked or malformed token or sessionWrong role, missing scope, IP/WAF block, file permissions

The decision rule#

  1. No credentials at all? Send 401 with a challenge.
  2. Credentials present but invalid or expired? Send 401. The client should log in again or refresh its token.
  3. Credentials valid, but this identity may not do this? Send 403.
  4. Should this caller even learn the resource exists? If not, send 404 instead of 403. RFC 9110 allows exactly this: a server that wishes to "hide" a forbidden resource "MAY instead respond with a status code of 404."

OAuth and bearer tokens#

Most modern APIs use bearer tokens, and RFC 6750 §3.1 spells out the mapping precisely:

Error code in WWW-AuthenticateMeaning (RFC 6750)Status
invalid_requestMissing or duplicated parameter, otherwise malformed400
invalid_tokenToken expired, revoked, malformed or otherwise invalid401
insufficient_scopeToken valid but needs higher privileges403
(no error code)Request had no authentication information at all401
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="example", error="invalid_token",
                  error_description="The access token expired"
HTTP/1.1 403 Forbidden
WWW-Authenticate: Bearer realm="example", error="insufficient_scope",
                  scope="invoices:write"

RFC 6750 also says that when a request has no authentication information, the server "SHOULD NOT include an error code or other error information," so the minimal challenge is just WWW-Authenticate: Bearer realm="example".

Wrong vs right#

Logged-out user requests /account → 403 Forbidden

401 Unauthorized + WWW-Authenticate (API), or a 302/303 to the login page (browser app)

A 403 tells the client that logging in will not help, so well-behaved clients will not try. For HTML pages, many sites redirect to a login form instead; that is fine as long as API endpoints still return a real 401.

Expired JWT → 403 Forbidden

Expired JWT → 401 with error="invalid_token"

Client libraries commonly use a 401 as the trigger to refresh the token and retry. A 403 breaks that flow and logs users out for no reason.

401 with no WWW-Authenticate header

401 always carries at least one challenge

It is a MUST in RFC 9110. If you genuinely have no challenge to offer (for example a cookie-session web app), consider whether a redirect to login or a 403 better describes the situation.

Regular user tries DELETE /admin/users/42 → 401

403 Forbidden, with a body that says the action needs the admin role

The user is authenticated; asking them to authenticate again is a loop. MDN's own 403 example is exactly this case.

Common causes of a 403 you did not expect#

If you are seeing 403s and your permission logic looks correct, the refusal often comes from somewhere other than your application code:

CauseWhere to look
Web server cannot read the file or directoryFile ownership and permissions; nginx/Apache error log
Directory with no index file and listing disabledMissing index.html; nginx logs a "directory index of … is forbidden" error
WAF, bot protection or CDN ruleCDN or firewall dashboard; response headers that name the vendor
IP allowlist or geo-blockServer allow/deny rules, cloud security groups
CSRF check failedFramework logs; Django, for example, returns a 403 when the CSRF token is missing or wrong
Signed URL expired or signature mismatchObject storage error body (for example S3's AccessDenied)
API rate limit reported as 403Some APIs do this: GitHub returns 403 or 429 for primary rate limits, with x-ratelimit-remaining: 0

How to debug#

# See the status line and the challenge header
curl -i https://api.example.com/v1/me

# Same request with a token: did the status change?
curl -i -H "Authorization: Bearer $TOKEN" https://api.example.com/v1/me

If the code is 401 without the token and still 401 with it, the token itself is the problem: check expiry, audience, issuer and clock skew. If it moves from 401 to 403, authentication works and the issue is permissions or scope. If it is 403 both times, the block is probably happening before your auth layer runs (WAF, IP rules, file permissions).

If you are a visitor#

  • Sign out and back in, or clear cookies for the site, to fix a stale session (that turns a 401 into a 200).
  • For a 403, check you are signed in with the right account; a work and a personal account are easy to mix up.
  • Turn off a VPN or proxy briefly. IP-based blocks and bot protection often show up as 403.
  • If none of that helps, the site has decided you may not see the page. Contact its owner.

SEO notes#

Google's documentation says 4xx codes other than 429 have no effect on crawl rate, and that indexed URLs returning 4xx are removed from the index. It specifically says: "Don't use 401 and 403 status codes for limiting the crawl rate." If you want crawlers to slow down, that is a job for 429 or 503, not an authentication error.

Frequently asked questions

What is the difference between 401 and 403?

401 Unauthorized means the request lacks valid authentication credentials, and the server must send a WWW-Authenticate challenge. 403 Forbidden means the server understood the request and refuses it; if credentials were sent, they are insufficient, and retrying with the same ones will not help.

Why is 401 called Unauthorized if it is about authentication?

It is a historical naming quirk. The name dates from early HTTP, but the definition in RFC 9110 is clearly about missing or invalid authentication credentials, not about permissions.

Should an expired token return 401 or 403?

401. RFC 6750, which defines OAuth 2.0 bearer tokens, says an expired, revoked or malformed token is invalid_token and the server should respond with 401. A valid token that lacks the needed scope is insufficient_scope and gets 403.

Is it OK to return 404 instead of 403?

Yes, when revealing that a resource exists would itself leak information. RFC 9110 explicitly allows a server that wants to hide a forbidden resource to respond with 404. GitHub does this for private repositories.

Does a 401 or 403 hurt SEO?

Google says 4xx codes other than 429 do not affect crawl rate, but indexed URLs that return 4xx are removed from the index. Google also warns not to use 401 or 403 to limit crawl rate.

Sources

Every factual claim on this page links to one of these. Checked October 2026. See how we source.

  1. RFC 9110 §15.5.2: 401 Unauthorized rfc-editor.org
  2. RFC 9110 §15.5.4: 403 Forbidden rfc-editor.org
  3. RFC 9110 §11.6.1: WWW-Authenticate rfc-editor.org
  4. RFC 6750: OAuth 2.0 Bearer Token Usage, §3.1 Error Codes rfc-editor.org
  5. MDN: 403 Forbidden developer.mozilla.org
  6. MDN: 401 Unauthorized developer.mozilla.org
  7. GitHub Docs: Troubleshooting the REST API docs.github.com
  8. Google Search Central: How HTTP status codes affect Google Search developers.google.com
  9. RFC 9457: Problem Details for HTTP APIs rfc-editor.org
  10. Django documentation: Cross Site Request Forgery protection docs.djangoproject.com