401 🌐 HTTP

HTTP 401 Unauthorized

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

Seen on: JavaScript Apache Nginx PHP REST API

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?

🧠 Still stuck? Analyze your error

Paste the full message, response headers or stack trace — we'll detect the platform and point to the most likely cause.