401 Status Code: What It Means and How to Fix It

The 401 status code is called "Unauthorized," but it actually means "unauthenticated" — and that naming mismatch has sent countless developers down the wrong debugging path.
When a server returns a 401, it isn't saying you don't have permission. It's saying it has no idea who you are. That single distinction changes everything about how you fix it.
This article is for developers, API consumers, and backend engineers who either need to resolve a 401 they're seeing right now or implement one correctly in their own API. Here's what you'll find inside:
- What a 401 actually means — the precise definition, why the name is misleading, and what the required
WWW-Authenticateheader tells you - The most common causes — missing credentials, expired tokens, stale cookies, and headers dropped by proxies like CloudFront
- 401 vs. 403 — a clear breakdown of when each code applies and why confusing them wastes debugging time
- How to fix a 401 — step-by-step paths for end users, API consumers, and backend engineers
- How to return 401 correctly in your own API — including when to use 401 vs. 403, why the
WWW-Authenticateheader is required, and what goes wrong when you get it wrong
The article moves from understanding to debugging to implementation, so you can read straight through or jump to the section that matches your situation.
What the 401 status code actually means
Before you can fix a 401 error or implement one correctly in your own API, you need a precise definition — and the name itself gets in the way.
"Unauthorized" sounds like a permissions problem. It isn't. Understanding exactly what the server is communicating when it returns a 401 is the foundation for everything else in this article.
What the HTTP specification actually says about 401
A 401 status code is an HTTP client error — part of the 4xx class — indicating that a request failed because it lacks valid authentication credentials for the requested resource. The official MDN definition is worth quoting directly: the 401 response "indicates that a request was not successful because it lacks valid authentication credentials for the requested resource."
The client-side classification matters. The problem isn't with the server's configuration or availability; it's with what the client sent (or failed to send) in the request. The server received the request just fine — it simply has no way to verify who is making it.
A concrete example from MDN illustrates this cleanly. A GET request to www.example.com/admin without credentials returns:
HTTP/1.1 401 Unauthorized
Date: Tue, 02 Jul 2024 12:18:47 GMT
WWW-Authenticate: Bearer
The server isn't saying the user is forbidden from /admin. It's saying it has no idea who the user is, and it needs a Bearer token before it can make that determination.
The required WWW-Authenticate header
Every valid 401 response must include a WWW-Authenticate header. This isn't a convention or a best practice — it's a specification requirement. Per MDN, this header "contains information on the authentication scheme the server expects the client to include to make the request successfully."
In the example above, WWW-Authenticate: Bearer tells the client that an access token is the required credential type. Other common values include Basic (for username/password encoded in Base64) and scheme-specific parameters that describe realm or token endpoint details.
If you receive a 401 without a WWW-Authenticate header, that's a signal of a misconfigured server, not a correctly implemented authentication gate. The header is what transforms a 401 from a dead end into actionable information — it tells the client exactly how to authenticate and try again.
Authentication failure, not a permissions problem
The word "Unauthorized" has caused genuine confusion among developers for years. MDN itself acknowledges it directly: "semantically this response means 'unauthenticated'".
The distinction is operationally critical. A 401 means the server cannot identify the requester — it has no verified identity to evaluate, because the request is missing credentials, carries invalid ones, or presents credentials that have expired. Crucially, re-authenticating could resolve the issue — that's the defining characteristic of a 401.
This distinction — authentication vs. authorization — is the most operationally important thing to understand about the 401 status code, and it's developed in full later in this article. For now, the working definition to carry forward is this: a 401 means the server doesn't know who you are, and the response must always tell you how to prove your identity.
Four authentication failures that produce a 401
A 401 error always traces back to an authentication failure — the server received a request but couldn't verify the identity of whoever sent it.
The specific trigger, though, varies enough that guessing at a fix without identifying the cause first usually wastes time. The four categories below cover the vast majority of 401s you'll encounter, whether you're hitting one in a browser or debugging an API integration.
Missing or incorrect credentials
The most straightforward cause: the request either carries no credentials at all, or the credentials provided are wrong. In a browser context, this means a user attempted to access a protected page without logging in, or entered the wrong username and password. In an API context, it means the Authorization header was either absent or contained an incorrect value — a wrong API key, a malformed token, or a typo in the secret.
MDN's canonical example illustrates this cleanly: a GET /admin HTTP/1.1 request sent to a protected endpoint with no Authorization header returns HTTP/1.1 401 Unauthorized with a WWW-Authenticate: Bearer header in the response. The server is telling the client exactly what it expected and didn't receive. If you're seeing a 401 status code for the first time on a given endpoint, start here — confirm that credentials are actually being sent and that they match what the server expects.
Expired or revoked tokens and sessions
This cause is distinct from wrong credentials: the credentials were once valid but are no longer accepted at the time of the request. OAuth access tokens have expiry times built in — a timestamp encoded inside the token itself (called the exp claim in JWT-based tokens). Once a token passes that expiry time, the server will reject it with a 401 even if the token is correctly formatted and was working minutes ago.
Session cookies behave similarly — a server-side session invalidation (triggered by a logout event, a password reset, or an administrative action) will cause previously valid session identifiers to be rejected. API keys can be revoked explicitly, which produces the same symptom. A developer integrating with an experimentation or feature flagging platform that gates access via secret Bearer tokens will hit this cause if a key is rotated on the server side without updating the client configuration. The request looks correct structurally, but the credential is no longer recognized.
Stale browser cache or cookies
End users sometimes encounter a 401 in a browser despite being confident they were just logged in. The usual culprit is a stale authentication cookie. After a server-side logout, password change, or session invalidation, the browser may continue sending an old session cookie automatically with each request. The server sees the cookie, recognizes it as invalid or expired, and returns a 401 — while the user sees a confusing error with no obvious explanation.
This is a client-side state problem, not a credential problem. The fix is clearing the relevant cookies or cache rather than re-entering a password, though the two are easy to conflate.
Missing or dropped authorization headers in API and proxy contexts
This cause is specific to API consumers and is easy to overlook because the credentials may be correct and present on the client side — but never reach the server. Intermediary layers like CDNs and reverse proxies can strip headers before forwarding requests to the origin.
AWS CloudFront is a documented example: by default, CloudFront does not forward the Authorization header to the origin server. If the origin requires that header for authentication, every request through CloudFront will produce a 401, even though the end client sent valid credentials. The fix requires explicitly including Authorization in the cache key policy or switching to the AllViewer origin request policy.
Beyond CDN stripping, this category also covers common API client mistakes: sending an API key as a query parameter when the server expects it in a header, or sending a Bearer token without the required Bearer prefix in the Authorization header value. The WWW-Authenticate header in the 401 response is the fastest way to diagnose which scheme the server expects — if it says Bearer, the client needs to send Authorization: Bearer , exactly in that format.
401 vs. 403: one is an identity problem, the other is a permissions problem
These two status codes are not interchangeable, and treating them as such is one of the most reliable ways to waste an afternoon debugging the wrong thing. The distinction is precise: 401 is an authentication failure, and 403 is an authorization failure. One means the server doesn't know who you are. The other means it knows exactly who you are and has decided you're not allowed in.
401 Unauthorized — the server doesn't recognize you
When a server returns a 401, it's saying it cannot identify the requesting client. Credentials are missing, invalid, or expired — and until the client provides valid ones, the server won't proceed.
Here's where the naming becomes a genuine source of confusion: the status code is called Unauthorized, but it functionally means Unauthenticated. This isn't a minor semantic footnote — it's the primary reason developers conflate 401 and 403. Experienced engineers acknowledge they have to pause and double-check which is which precisely because the label doesn't match the behavior. The key operational implication is that re-authenticating could resolve a 401. The path forward exists — the client just needs to prove its identity first.
403 Forbidden — the server knows you, but says no
A 403 response comes from a server that has successfully identified the user and is still refusing access. The credentials are valid. The identity check passed. The denial is about permissions, not identity.
Concrete scenarios that correctly produce a 403 include: a user whose role doesn't grant access to a specific resource, an IP address that has been blacklisted, or an access control policy that denies the request regardless of authentication state. In all of these cases, re-entering credentials or refreshing a token accomplishes nothing — the problem isn't who you are, it's what you're allowed to do.
RFC 9110 draws this line cleanly: a 401 implies the client could gain the authority needed and try again; a 403 means the request is simply forbidden, and no amount of re-authenticating will change that outcome.
Misreading 401 as 403 sends debugging in the wrong direction
Getting this wrong has real consequences in both directions. A developer who receives a 403 and treats it as a 401 will spend time refreshing tokens, re-entering credentials, or rotating API keys — none of which addresses the actual problem, which is a permissions or role configuration issue. The inverse is equally wasteful: treating a 401 as a 403 can lead someone to escalate a permissions ticket when they simply need to log in again.
The shorthand that holds up well in practice: "401 — we don't know who you are. 403 — you're not allowed to do that."
For API designers, the distinction carries additional weight. Use 401 only when re-authentication could plausibly resolve the issue — when the client genuinely lacks valid credentials or when a token has expired and can be refreshed. Use 403 when the denial is final regardless of credentials: the user's role doesn't permit the action, the resource is outside their scope, or the access control policy is categorical. Returning a 403 when you mean 401 (or vice versa) forces API consumers to implement incorrect error-handling logic and makes automated retry and re-authentication flows unreliable.
A useful real-world illustration: accessing a project within your own organization might return a 401 if you haven't authenticated yet — authenticate, and you're in. Attempting to access a project in a completely separate organization would correctly return a 403 — no credential you could ever provide would grant that access, because the restriction is structural, not identity-based.
The naming irony is worth internalizing once and then moving past: 401 means unauthenticated, 403 means unauthorized. Once that mapping is locked in, the debugging path from each code becomes unambiguous.
Fixing a 401 depends on where the authentication failure is occurring
The right fix for a 401 depends entirely on where the authentication failure is occurring — and that location is different for a user who can't log into a website, a developer whose API calls are getting rejected, and a backend engineer whose middleware is misconfigured. Before attempting any fix, there's one diagnostic step that applies to everyone.
Start by reading the WWW-Authenticate header
Every valid 401 response is required by the HTTP specification is explicit: a 401 response must include a WWW-Authenticate header. This header doesn't just confirm that authentication failed — it tells you exactly which authentication scheme the server expects. Skipping this step and guessing at a fix is how debugging sessions turn into rabbit holes.
In practice, finding this header takes about ten seconds. In browser DevTools, open the Network tab, select the failing request, and look under Response Headers. In curl, run with the -v flag. In Postman, it appears in the response headers panel.
The MDN-documented example is instructive: a GET /admin request that returns WWW-Authenticate: Bearer is telling you the server expects an OAuth access token — not a username and password, not an API key in a query parameter. The header content directly determines which fix path applies.
Fixes for end users
If you're hitting a 401 on a website or web application, the most common culprits are stale credentials and cached session data. Start by re-entering your credentials carefully — caps lock and autofill errors account for a surprising share of these failures. If that doesn't resolve it, clear your browser's cache and cookies, then attempt a fresh login. A private or incognito window is a fast way to confirm whether cached session data is the issue, since it starts with a clean state.
If the error persists after a fresh login, try a different network. Some 401 responses are triggered by IP-level restrictions rather than credential problems, and switching networks rules that out quickly. For desktop applications, stored credentials in your operating system's credential manager — Windows Credential Manager, for example — can become stale after a password change. Deleting the stored entry and re-authenticating forces the application to use your current credentials.
If none of these steps resolve the error, the problem is likely on the server side: an expired or locked account, or a temporary authentication service outage.
Fixes for API consumers and developers
Once you've confirmed the required scheme from the WWW-Authenticate header, the fix becomes more targeted. For Bearer token authentication — the most common scheme in modern APIs — verify that the token is present in the Authorization header with the correct prefix (Authorization: Bearer ), and confirm it hasn't expired. Expired tokens are the single most frequent cause of 401s in OAuth-based integrations. If the token is expired, use your refresh token to obtain a new access token rather than re-authenticating from scratch.
For API key authentication, check that the key is being sent in the location the API expects — some APIs require it in a header, others in a query parameter, and sending it to the wrong place produces a 401 even if the key itself is valid. Also confirm the key is active and hasn't been revoked, and that it carries the correct scope for the endpoint you're calling. A key scoped to read-only access will produce a 401 on a write endpoint on some platforms.
A common mistake worth flagging: sending a token with the wrong prefix. Using Token instead of Bearer, or omitting the prefix entirely, will cause the server to reject the credential even if the token value is correct.
Server-side fixes for backend engineers
If you own the server returning the 401, the first thing to verify is that your authentication middleware is applied to the correct routes. A 401 on a public endpoint is a strong signal that middleware is over-applied. Conversely, a protected endpoint that returns 200 without credentials means it's under-applied.
Equally important: confirm your server is always returning a WWW-Authenticate header alongside every 401 response. Omitting it violates the HTTP specification and breaks client-side error handling — API consumers cannot programmatically determine the correct authentication scheme without it.
Beyond middleware configuration, audit your token validation logic. Ensure the server is correctly verifying token signatures, checking expiry timestamps, and validating that the token was issued by the expected authentication provider — not just that it's structurally valid. For session-based authentication, confirm the session store is healthy and that session expiry is configured intentionally.
When the problem is temporary
Occasionally, credentials are valid and configuration is correct, but the server is still returning 401s due to an authentication service outage or a transient misconfiguration. Microsoft's support documentation explicitly identifies "a temporary problem on the server side" as a recognized cause of 401 errors. In this scenario, the practical response is to retry after a short delay, check the service's status page, and contact the API provider if the error persists despite confirmed-valid credentials.
Returning 401 correctly in your API
When you return a 401, you are starting an authentication conversation between a client and a server — and that conversation is surprisingly easy to get wrong. For API designers, the 401 status code is not just a signal that something failed; it is a structured protocol message that carries specific obligations. Getting those obligations right has direct consequences for how clients, SDKs, and automated systems respond.
When to return 401 vs. 403
The decision rule is straightforward once you internalize it: return 401 when re-authenticating could fix the problem, and return 403 when it cannot. If the request is missing a token, carries an expired token, or presents invalid credentials, 401 is correct — the client can resolve the issue by authenticating properly. If the client's identity is known and valid but the server is still denying access, that is a 403.
This is not a semantic preference. It directly affects retry logic, SDK error handling, and how automated agents decide what to do next. A 401 tells the client "try again with credentials." A 403 tells the client "stop — credentials won't change the outcome." Returning 403 when you mean 401 sends clients down a dead-end debugging path. Returning 401 when you mean 403 can trigger unnecessary token refresh cycles or re-authentication flows that will never succeed.
Including the WWW-Authenticate header is a specification requirement, not a convention
The HTTP specification is explicit: a 401 response must include a WWW-Authenticate header. This header tells the client which authentication scheme is expected, giving it the information it needs to retry correctly. A correct response to a protected endpoint looks like this:
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer
Content-Type: application/json
{ "error": "invalid_token" }
Many APIs omit this header entirely. Doing so violates the spec, and the consequences are practical, not just theoretical — clients, proxies, and authentication libraries depend on WWW-Authenticate to determine how to respond. Without it, a client has no machine-readable signal about what kind of credentials to supply. The JSON error body helps humans debugging the response, but the WWW-Authenticate header is what automated systems act on.
Downstream consequences of getting it wrong
A misused 401 in a browser produces a confusing error message. The same mistake in a system with automated retry logic can silently break an entire workflow before anyone realizes what went wrong — and the root cause will be misclassified in your observability tooling because the status code is wrong at the source.
In an OAuth-based integration, a misused 401 can trigger a token refresh storm: the client sees a 401, assumes its token expired, automatically requests a new one, uses the new token, gets another 401 — and repeats this cycle indefinitely because the real problem was never a token expiry. These failures are often silent and difficult to trace because the status code is wrong at the source.
This risk is amplified in modern automated environments. APIs are increasingly called by CI/CD pipelines, LLM agents, code generation tools, and automated test runners that do not behave like human users. These systems rely heavily on status codes to make branching decisions. A misused 401 in that context does not just confuse a developer — it can break entire automated workflows before anyone realizes what went wrong.
Real-world application: API keys and feature flagging
Feature flagging platforms illustrate the correct design pattern clearly. Consider an API where management endpoints — creating, updating, or deleting feature flags — require a secret API key using Bearer or Basic authentication. A request to one of those endpoints without valid credentials should return a 401, signaling that the client needs to authenticate to proceed.
At the same time, the endpoint used by SDKs to read flag values at runtime is intentionally public and requires no authentication at all. GrowthBook's architecture reflects this split directly: protected REST API endpoints for managing flags and experiments carry explicit authorization requirements, while the endpoint used by GrowthBook's SDKs to fetch flag configurations at runtime is intentionally public by default. The design principle is deliberate — not every endpoint should be behind authentication, and the ones that are should return 401 (not 403) when credentials are absent or invalid.
The practical implication for API designers is to be intentional about which endpoints require authentication, implement the correct status code for each failure mode, and always include the WWW-Authenticate header when returning 401. The header is not optional boilerplate — it is the part of the response that makes the error actionable.
The mental model that makes 401s stop being mysterious
Everything covered in this article flows from one core insight: a 401 means the server doesn't know who you are, and the response must always tell you how to prove your identity. Once that framing is locked in, most 401s stop being mysterious and start being mechanical — the debugging paths, the 401 vs. 403 distinction, and the WWW-Authenticate header requirement all follow naturally from it.
Quick reference: 401 vs. 403 decision checklist
The shorthand that holds up in practice: if re-authenticating could fix it, return 401; if it can't, return 403. A missing token, an expired token, and invalid credentials all belong to 401. A valid identity that lacks permission belongs to 403. Getting this wrong doesn't just confuse developers — it breaks automated retry logic, triggers token refresh storms, and sends CI/CD pipelines and LLM agents down paths that will never resolve.
Checklist for fixing a 401 error based on your role
The first move is always the same regardless of your role: read the WWW-Authenticate header. It tells you the expected authentication scheme, which determines every subsequent step. If you're an end user, start with cache and cookies before assuming your credentials are wrong. If you're an API consumer, confirm the token is present, correctly prefixed, and not expired before checking anything else. If you own the server, verify middleware scope and confirm you're always returning the WWW-Authenticate header — omitting it violates the spec and makes your 401s harder to act on than they need to be.
A wrong status code in an automated system fails silently and at scale
Being precise about 401 vs. 403 feels like a small implementation detail, but its consequences scale with system complexity. In a simple app, a misused status code produces a confusing error. In a system with automated agents, it can silently break entire workflows. The WWW-Authenticate header is the piece most often skipped, and it's the piece that makes the difference between a 401 that's actionable and one that's a dead end. The same principle applies across any API that separates protected management endpoints from public SDK or read endpoints — not every endpoint should be behind a credential gate, and the ones that are should signal authentication failures precisely.
The goal of this article was to give you a clear mental model and a practical path forward, whether you're debugging a 401 right now or designing an API that returns them correctly.
What to do next: Start with where you are. If you're hitting a 401 right now, open DevTools or run curl -v and read the WWW-Authenticate header before doing anything else — that header tells you which fix path applies. If you're building an API, audit your 401 responses: confirm the header is present on every one, and check whether each failure mode is genuinely an authentication problem (401) or a permissions problem (403). That single audit will catch the most common implementation mistakes before they reach production.
Related insights
Related Articles
Ready to ship faster?
No credit card required. Start with feature flags, experimentation, and product analytics—free.

