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: Use 401 when the client still needs valid credentials. Use 403 when the server knows the client identity but refuses the requested action.

401 Unauthorized
vs
403 Forbidden

The Fastest Way To Decide

Ask two questions in order:

  1. does the server know who this client is
  2. if yes, is this identity allowed to do this

If the answer to the first question is no, return 401. If the answer to the first question is yes but the second is no, return 403.

The Core Difference

Despite the name, 401 Unauthorized is really about authentication. 403 Forbidden is about authorization.

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 Header That Usually Gives 401 Away

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 carry an authentication challenge, because the problem is not "please authenticate." It is "authentication did not unlock this action."

## 403 vs 404 When You Want To Hide Existence

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 the client never authenticated successfully, the server is not at the permission question yet.

**Returning 401 for permission failures** — telling a logged-in user to "authenticate again" when they simply lack access is misleading and makes client behavior worse.

**Using 403 for rate limiting** — rate limiting is `429 Too Many Requests`, not an auth decision.

**Forgetting to log real 403s** — they are often valuable signals for permission bugs or access probing.

## 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.