# HTTP 401 Unauthorized — Meaning, Causes & Fixes

> The request lacks valid authentication credentials — the server doesn't know who you are.

Despite its name, 401 is about authentication, not authorization: the server doesn't know who you are. Either no credentials were sent, or the ones sent — token, cookie, API key — are missing, expired or invalid. The response must include a `WWW-Authenticate` header describing how to authenticate.

- Class: 4xx Client Error
- Retry: No — change the request first
- Cacheable by default: no
- Defined in: [RFC 9110 §15.5.2 — 401 Unauthorized](https://www.rfc-editor.org/rfc/rfc9110.html#section-15.5.2)

## Example response

```http
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="api"
Content-Type: application/json
Content-Length: 24

{"error":"Unauthorized"}
```

## Common causes

- No `Authorization` header or session cookie was sent.
- An expired or revoked access token (a JWT whose `exp` is in the past).
- A token for the wrong environment or audience, such as a staging key on production.
- A proxy, redirect or CORS configuration stripping the `Authorization` header.

## How to fix it

1. Decode the token and check its `exp`, `aud` and `iss` claims.
2. Refresh the access token and retry once; if that fails, send the user to log in again.
3. Check the header format: `Authorization: Bearer <token>`, with one space.
4. For cross-origin calls, make sure the server allows `Authorization` in `Access-Control-Allow-Headers`.

## Send a 401 from your server

### Express

```
res.set("WWW-Authenticate", "Bearer realm=\"api\"");
res.status(401).json({ error: "Unauthorized" });
```

### Next.js route handler

```
return Response.json({ error: "Unauthorized" }, { status: 401, headers: { "WWW-Authenticate": "Bearer realm=\"api\"" } });
```

### Flask

```
return {"error": "Unauthorized"}, 401, {"WWW-Authenticate": "Bearer realm=\"api\""}
```

### Go net/http

```
w.Header().Set("WWW-Authenticate", "Bearer realm=\"api\"")
http.Error(w, "Unauthorized", http.StatusUnauthorized)
```

### nginx

```
add_header WWW-Authenticate "Bearer realm=\"api\"" always;
return 401;
```

## FAQ

### 401 vs 403 — what's the difference?

401: we don't know who you are — authenticate and try again. 403: we know who you are, and you're not allowed. Logging in again fixes a 401; it won't fix a 403.

### Why does a 401 open a browser login popup?

The response carried WWW-Authenticate: Basic, and browsers show their native credential dialog for Basic auth. APIs should use the Bearer scheme to avoid it.

## Related status codes

- [403 Forbidden](https://www.devkult.com/http/403.md)
- [407 Proxy Authentication Required](https://www.devkult.com/http/407.md)
- [400 Bad Request](https://www.devkult.com/http/400.md)

Open the interactive page: https://www.devkult.com/http/401