Developer Tools 10 min readBy Mehadi ShawonPublished Updated

HTTP 403 vs 401 Explained (With API Examples)

HTTP 401 vs 403: authentication vs authorization, when to return each, common causes in APIs and web servers, and how to fix them.

HTTP status codes 401 and 403 shown side by side with padlock icons
Quick answer

HTTP 403 vs 401 Explained (With API Examples)

HTTP 401 Unauthorized means the request lacks valid authentication — the server doesn't know who you are. HTTP 403 Forbidden means you are authenticated, but the server refuses to let you access this resource. 401 says 'log in first'; 403 says 'you're logged in but not allowed'.

Every developer has stared at a 401 or 403 wondering which one their code should actually return. The distinction matters because clients, browsers, proxies, and monitoring tools all behave differently based on the status code. This guide clears it up with real examples.

Table of Contents

  • The one-sentence difference
  • Authentication vs authorization
  • When to return 401
  • When to return 403
  • Real API examples
  • How browsers behave with each
  • Server configuration examples (nginx, Apache, Express)
  • Common misuse patterns
  • Security best practices
  • Troubleshooting
  • FAQ
HTTP status codes 401 and 403 shown side by side with padlock icons

The One-Sentence Difference

401 = 'I don't know who you are.' 403 = 'I know who you are, and you can't have this.'

Authentication vs Authorization

Authentication is proving who you are — a password, an API token, a signed JWT. Authorization is what you're allowed to do — read this record, edit this user, delete this file.

A request that fails authentication returns 401. A request that authenticates but fails authorization returns 403.

When to Return 401

  • No Authorization header at all on a protected endpoint.
  • Malformed or invalid token (bad signature, wrong format).
  • Expired session cookie or refresh token.
  • Basic-auth credentials rejected.

Always include a WWW-Authenticate response header: WWW-Authenticate: Bearer realm="api" for JWT APIs, or Basic realm="admin" for HTTP Basic.

Inspect a live 401/403 response header in your browser with our HTTP Headers tool.

Check HTTP Headers

When to Return 403

  • Authenticated user tries to access another user's data.
  • User with 'viewer' role hits an admin-only endpoint.
  • Read-only API key attempts a write operation.
  • IP address blocked by WAF or rate limit.
  • Feature flag disabled for this account.

Real API Examples

GET /api/users/me — no token

Correct: 401 Unauthorized, body { error: 'authentication_required' }, header WWW-Authenticate: Bearer.

GET /api/users/me — expired token

Correct: 401 Unauthorized. Client refreshes token, retries.

DELETE /api/users/999 — valid viewer token, admin-only route

Correct: 403 Forbidden, body { error: 'insufficient_permissions', required: 'admin' }.

GET /api/users/123 — valid token for user 456

Correct: 403 (or 404 if you don't want to leak that user 123 exists).

Ad Space

How Browsers Behave with Each

On 401 with WWW-Authenticate: Basic, browsers pop up a native username/password dialog. On 401 with Bearer, browsers do nothing special — your JavaScript handles it. On 403, no browser prompt fires — the response is treated like any other error.

Server Configuration Examples

nginx

location /admin { auth_basic "Restricted"; auth_basic_user_file /etc/nginx/.htpasswd; } — missing/invalid creds return 401. Blocked by IP (deny 1.2.3.4;) returns 403.

Apache

Same conventions via .htaccess: AuthType Basic + Require valid-user for 401 protection; Require ip 10.0.0.0/8 for 403-style IP restrictions.

Express (Node.js)

Return res.status(401).set('WWW-Authenticate', 'Bearer').json({...}) when no token, and res.status(403).json({error:'forbidden'}) when the token is valid but role is wrong.

Common Misuse Patterns

  • Returning 403 for missing token — clients don't know to prompt login.
  • Returning 401 for permission denials — clients uselessly try to refresh tokens.
  • Returning 401 without WWW-Authenticate — violates RFC 9110 and confuses clients.
  • Returning 200 with 'error' in the body — breaks monitoring and retries.
  • Returning 403 to hide existence when 404 would be safer against enumeration attacks.

Security Best Practices

  • Never leak whether a username exists in 401 responses — same status and message whether the account exists or not.
  • Include no stack traces or internal details in 403 bodies.
  • Log 401/403 with the offending IP for rate-limiting.
  • Rotate API keys periodically to force clean 401s if a key leaks.
  • Consider returning 404 instead of 403 for private resources to prevent enumeration.

Troubleshooting

  1. Getting 401 unexpectedly? Check that Authorization header is actually sent (proxies sometimes strip it).
  2. Getting 403 from your own code? Add logging around the permission check to see which rule fires.
  3. Getting 403 from Cloudflare? Check WAF events in the Cloudflare dashboard.
  4. Getting alternating 401 and 200? A token cache is inconsistent — clear and retry.

See every response header a URL returns.

Open HTTP Headers Checker

Frequently Asked Questions

Is 401 or 403 the right response for an expired token?+

401. Expired tokens are an authentication failure — the client should refresh and retry. 403 implies the token is valid but the user still can't access this resource.

Why does WordPress return 403 for /wp-admin?+

Because you're not logged in, WordPress considers the request unauthorized to see the admin page. Technically 401 would be more correct, but many CMS platforms return 403 by convention.

Should I include a WWW-Authenticate header on 401?+

Yes. RFC 9110 requires it. It signals the auth scheme (Bearer, Basic, Digest) the client should use.

What's the difference between 403 and 404?+

403 says 'this exists but you can't have it'. 404 says 'this does not exist'. Some sites return 404 instead of 403 to hide the existence of private resources.

Can Cloudflare or a WAF return 403?+

Yes. Cloudflare, AWS WAF, and similar services return 403 for blocked requests (rate limits, bot detection, geo blocking). Check the response body — it usually says 'blocked by security rules'.

Why does my nginx return 403 for a static file?+

Usually filesystem permissions — nginx can't read the file — or a missing index.html when directory listing is off. Check the nginx error log.

Does 401 or 403 count as a client error or server error?+

Both are 4xx — client errors. They mean the client did something wrong (didn't authenticate, or authenticated but requested something not allowed).

Should APIs return 200 with an error body instead of 401/403?+

No. Use the correct status code so browsers, proxies, and monitoring tools handle it correctly. Include structured error details in the body.

Ad Space

Try the related free tools

Hands-on utilities from DigiMetrics Hub that go with this guide.

All tools