HTTP

Comparison

401 vs 403: Authentication vs Authorization

Understand the difference between 401 Unauthorized and 403 Forbidden. Learn when each status code applies, common mistakes, and how to use them correctly in APIs.

Bottom line: 401 means "who are you?" — the request lacks valid credentials. 403 means "I know who you are, but you cannot do this" — the request is authenticated but not authorized.

401 Unauthorized
vs
403 Forbidden

The Core Difference

Despite the name, 401 is about authentication (identity), not authorization (permission). 403 is about authorization (permission).

The naming is a historical accident — 401 was named “Unauthorized” but its semantics are about authentication. RFC 7235 clarifies this explicitly.

When Each Applies

ScenarioCorrect Code
No Authorization header sent401
Invalid or expired token401
Valid token, but user lacks permission403
Admin-only resource accessed by regular user403
Resource exists but is hidden from this user403 (or 404)
IP blocklist403
Rate limit exceeded429 (not 403)

The WWW-Authenticate Header

A 401 response must include a WWW-Authenticate header that tells the client how to authenticate:

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="api", error="invalid_token"
```text

A 403 response does not include `WWW-Authenticate` — there's no authentication challenge because the problem isn't missing credentials, it's insufficient permissions.

If you return a 401 without `WWW-Authenticate`, you're technically violating the HTTP spec (RFC 7235 §3.1).

## Security: 403 vs 404

There's a deliberate security pattern of returning 404 instead of 403 for resources that exist but the user shouldn't know about. If your API returns 403 for `/admin/users`, you've confirmed to an attacker that the endpoint exists. Returning 404 reveals nothing.

This is called "security through obscurity" and is a valid defense-in-depth measure for sensitive resources. The tradeoff: it makes debugging harder for legitimate users.

## Browser Behavior

Browsers handle 401 specially: they may show a native authentication dialog (for `Basic` auth challenges) or trigger your app's login flow. A 403 gets no special browser treatment — it's just an error response.

## Common Mistakes

**Returning 403 when credentials are missing** — if no token is provided at all, return 401. Reserve 403 for cases where you've verified identity but denied access.

**Returning 401 for permission failures** — if a logged-in user tries to access another user's data, that's 403, not 401. Returning 401 tells them to "try logging in again," which is confusing and incorrect.

**Returning 403 without logging** — 403s from authenticated users are worth logging. They can indicate misconfigured permissions, a bug in your authorization logic, or a user probing for access they shouldn't have.

**Using 401 for rate limiting** — rate limiting is 429 Too Many Requests. Using 401 or 403 for rate limits confuses clients into thinking it's an auth problem.

## API Design Guidance

For REST APIs:

```http
GET /api/profile          → 401 (no token)
GET /api/profile          → 200 (valid token, own profile)
GET /api/users/other-id   → 403 (valid token, not your profile)
GET /api/admin/dashboard  → 403 (valid token, not an admin)
```text

For the 403 case where you want to hide resource existence:

```http
GET /api/users/other-id   → 404 (valid token, resource hidden)

Choose 403 when the user should know the resource exists but they can’t access it. Choose 404 when revealing the resource’s existence is itself a security concern.