What 401 means
The request lacks valid authentication credentials for the resource. 401 is defined in RFC 9110 §15.5.2, and despite the name it's about authentication (who are you?), not authorization (are you allowed?). The server must send a WWW-Authenticate header (§11.6.1) with at least one challenge saying how to authenticate:
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="api", error="invalid_token", error_description="The access token expired"
Content-Type: application/json
{"error": "token expired"}
If the request already carried credentials, the 401 means they were refused. When the user is authenticated but not permitted, the right answer is 403 Forbidden.
Common causes
- No credentials sent: a missing
Authorizationheader, or a cookie not sent because ofSameSite, a different domain, orfetchwithoutcredentials: 'include'. - Expired or revoked tokens: OAuth access tokens and JWTs past their
exp, or a session the server forgot after a restart. - The wrong scheme or format:
Authorization: Token abcwhen the API expectsBearer abc, or Basic credentials not base64-encoded asuser:password. - A proxy that strips
Authorization: some proxies, CDNs and PHP-FPM/CGI setups don't pass the header through. - CORS preflights requiring auth. The browser's
OPTIONSpreflight never carries credentials, so an API that demands them onOPTIONSfails before the real request is sent. - Clock skew between servers, making a fresh token look not yet valid or already expired.
How to fix it as a client
- Check the
WWW-Authenticateheader and body: they often say whether the token is missing, malformed or expired. - Refresh the access token and retry once; if that fails, send the user to log in again.
- Inspect the token (for a JWT, decode it and check
exp,audandiss). - Confirm the exact header the API expects, including the scheme name.
How to fix it on the server
- Always include
WWW-Authenticate. WithBasic, browsers show their login dialog; for APIs,Bearerwith anerrorcode (RFC 6750) helps clients. - Let
OPTIONSrequests through without authentication and answer them with the CORS headers. - Apache with PHP: pass the header on with
CGIPassAuth On. nginx with FastCGI: make sureHTTP_AUTHORIZATIONreaches the app (it does by default for proxied headers; check custom configs). - Distinguish 401 (log in) from 403 (you're logged in, but no) so clients know whether re-authenticating can help.
401 and SEO
Google can't index pages behind authentication: a 401 is a 4xx, so the URL is treated as unavailable and not indexed. That makes HTTP authentication a reliable way to keep a staging site out of search, better than robots.txt, which only blocks crawling and lets URLs be indexed from links. Don't put public content behind a 401 for some user agents, and don't use 401 to slow crawlers down.
Related codes
- 403 Forbidden: authenticated (or not) and refused anyway.
- 407 Proxy Authentication Required: the same idea, for a proxy.
- 400 Bad Request: the request itself is malformed.
Send the request with and without your Authorization header and compare the responses in the HTTP Request Builder.