# HTTP 401 Unauthorized

**Code:** `401` · **Category:** HTTP · **Seen on:** JavaScript, Apache, Nginx, PHP, REST API

The request has no valid authentication credentials — you need to log in or send a valid token.

## Meaning

Despite the name, 401 is about authentication (who you are), not authorization. The server is saying “I don’t know who you are” — the credentials are missing, expired, malformed or wrong.

A correct 401 response includes a `WWW-Authenticate` header telling the client which scheme to use (e.g. `Bearer`). If you are logged in but still not allowed, the right code is 403.

## Common causes

- No `Authorization` header sent
- Expired access token / JWT
- Wrong scheme, e.g. `Authorization: <token>` instead of `Authorization: Bearer <token>`
- Invalid or revoked API key
- Session cookie not sent (cross-site request without `credentials: 'include'`, SameSite rules)
- Proxy or web server strips the Authorization header before it reaches the app (common with Apache + PHP-FPM)
- Clock skew making a valid JWT look expired or not-yet-valid

## Quick fix

1. Confirm the request actually carries the `Authorization` header (browser DevTools → Network)
2. Use the exact scheme the API expects: `Bearer <token>`
3. Refresh or regenerate the token / API key
4. For cookies across origins, send `credentials: 'include'` and set `SameSite=None; Secure`
5. On Apache, pass the Authorization header through to PHP (see fix below)

## Detailed fix by platform

### JavaScript

1. Send the token with the right scheme and handle refresh on 401.

```javascript
const res = await fetch('/api/me', {
  headers: { Authorization: `Bearer ${accessToken}` },
  credentials: 'include', // only if you rely on cookies
});
if (res.status === 401) {
  await refreshToken();   // then retry once
}
```


### Apache

1. Apache with CGI/FPM often drops `Authorization`. Add to `.htaccess`:
2. Forward the header to PHP:

```apache
RewriteEngine On
RewriteCond %{HTTP:Authorization} .
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
```


### PHP

1. Read the header from `$_SERVER['HTTP_AUTHORIZATION']` and fall back to `REDIRECT_HTTP_AUTHORIZATION`.
2. Return 401 with `WWW-Authenticate: Bearer` for missing/invalid tokens and 403 for valid-but-forbidden users.

### Nginx

1. If Nginx proxies to an app, make sure you don’t override the header: avoid `proxy_set_header Authorization "";`.
2. For basic auth zones, check `auth_basic_user_file` path and password hash format.

## Code examples

### Decode a JWT to check expiry

```bash
# Payload is the middle part; "exp" is a Unix timestamp
echo "$TOKEN" | cut -d. -f2 | base64 -d 2>/dev/null; echo
date +%s   # compare with now
```

If `exp` is in the past (or your server clock is wrong), the server will answer 401.

## How to diagnose

1. **Request** — Is an Authorization header or session cookie present in the outgoing request?
2. **Format** — Is the scheme right (Bearer/Basic) and the token complete?
3. **Token validity** — Expired? Revoked? Issued for a different audience/environment?
4. **Transport** — Does a proxy, CDN or Apache config strip the header?
5. **Server** — What does the auth middleware log as the rejection reason?

---
Source: [WhatIsMyError](https://whatismyerror.com/errors/http/401-unauthorized)
