httpseverity: can-fix
401

HTTP 401 Unauthorized — What It Means and How to Fix It

401 Unauthorized

95% fixable~10 mindifficulty: beginner

Verified against RFC 9110 §15.5.2 (HTTP Semantics), RFC 6750 (Bearer Token Usage), MDN Web Docs: 401 Unauthorized · Updated June 2026

> quick_fix

The server requires valid authentication credentials and did not receive them. Check that your Authorization header is present, correctly formatted (`Bearer <token>`), and that the token hasn't expired. The `WWW-Authenticate` response header tells you the expected auth scheme.

# Check the WWW-Authenticate header in the 401 response
curl -i https://api.example.com/protected
# HTTP/2 401
# www-authenticate: Bearer realm="api", error="missing_token"

# Retry with a valid token
curl -i https://api.example.com/protected \
  -H 'Authorization: Bearer eyJhbGci...'

What causes this error

HTTP 401 means the request lacked valid authentication. Common causes: missing `Authorization` header, expired JWT or session token, malformed Bearer token (extra whitespace, missing `Bearer ` prefix), token issued for a different audience or issuer, cookie not sent due to `SameSite` restrictions, or the `Authorization` header being stripped by a reverse proxy.

> advertisementAdSense placeholder

How to fix it

  1. 01

    step 1

    Check the WWW-Authenticate response header

    RFC 9110 requires the server to include a `WWW-Authenticate` header on 401 responses describing the expected auth scheme. It often includes an `error` param (`invalid_token`, `expired_token`, `missing_token`) that pinpoints the exact failure.

    curl -si https://api.example.com/endpoint | grep -i 'www-authenticate'
  2. 02

    step 2

    Decode your JWT and check expiry

    Copy your token to jwt.io and check the `exp` claim. Tokens expire by design. If `exp` is in the past, your client needs to refresh the token before retrying. In production, build token refresh logic — don't rely on long-lived tokens.

    // Decode JWT payload (no verification — only for debugging)
    const payload = JSON.parse(atob(token.split('.')[1]))
    console.log('Expires at:', new Date(payload.exp * 1000))
    console.log('Issued for:', payload.aud, payload.iss)
  3. 03

    step 3

    Verify the Authorization header format

    The header must be exactly `Authorization: Bearer <token>` — one space between `Bearer` and the token, no extra quotes, no newlines. A common mistake is including the word 'Bearer' twice or adding a trailing space.

    # Correct
    curl -H 'Authorization: Bearer eyJhbGci...token...'
    
    # Wrong (double Bearer)
    curl -H 'Authorization: Bearer Bearer eyJhbGci...'
    
    # Wrong (Basic instead of Bearer)
    curl -H 'Authorization: Basic eyJhbGci...'
  4. 04

    step 4

    Check if the proxy is stripping the Authorization header

    Nginx, AWS ALB, and some API gateways strip `Authorization` headers in certain configurations — especially when `proxy_pass` is used without forwarding headers explicitly. Add `proxy_set_header Authorization $http_authorization;` in Nginx.

    # nginx.conf
    location /api/ {
        proxy_pass http://backend;
        proxy_set_header Authorization $http_authorization;
        proxy_set_header Host $host;
    }
  5. 05

    step 5

    Debug cookie-based auth with SameSite issues

    If using session cookies, cross-origin requests won't send cookies unless `SameSite=None; Secure` is set. Check Chrome DevTools → Application → Cookies to confirm the cookie is being sent. Also ensure the server sets `Access-Control-Allow-Credentials: true` and the client uses `credentials: 'include'`.

    // Fetch with credentials (cookies sent cross-origin)
    const res = await fetch('https://api.example.com/me', {
      credentials: 'include',  // required for cross-origin cookies
    })

How to verify the fix

  • The 401 no longer appears after sending a valid, non-expired token.
  • The `WWW-Authenticate` header is absent from successful responses.
  • Token refresh logic is in place so sessions don't expire silently in production.

Why 401 happens at the runtime level

HTTP 401 is defined in RFC 9110 as the server's signal that the target resource requires authentication. The server must also send a `WWW-Authenticate` header indicating the acceptable auth scheme. At the implementation level, auth middleware runs before route handlers and short-circuits the request pipeline on failure. JWTs are validated by verifying the signature against the server's public key or secret, then checking `exp`, `iss`, and `aud` claims. Any of these checks failing produces a 401 without the route handler ever executing.

Common debug mistakes for 401

  • Not including `Bearer ` prefix in the Authorization header — just sending the raw token value.
  • Fetching a token in one environment (dev) and using it in another (prod) where the secret/key differs.
  • Middleware order: auth middleware running before CORS middleware, causing preflight OPTIONS requests to get 401.
  • JWT `aud` claim mismatch — token issued for `api.example.com` rejected by `api2.example.com`.
  • Session cookie with `SameSite=Strict` not sent on cross-origin requests from a different subdomain.

When 401 signals a deeper problem

A 401 in production at scale often signals absent token refresh infrastructure. Short-lived JWTs (15-minute expiry) are best practice but require a token rotation mechanism — refresh tokens stored in httpOnly cookies, a `/auth/refresh` endpoint, and client-side interceptors that transparently retry on 401. Without this, users get silently logged out mid-session. Building token refresh correctly means handling concurrent refresh calls (only one refresh at a time, queue others), storing refresh tokens securely, and rotating them on each use to prevent replay attacks.

Editor's take

The 401 is deceptively simple until you're building a microservices system where eight services all need to validate JWTs issued by a central auth service. The worst production scenario: the auth service rotates its signing key as part of a security incident response, every downstream service's token validation starts returning 401, and you have a full-stack outage while teams scramble to push JWKS endpoint changes to each service. Services that hard-code the public key instead of fetching it from a JWKS endpoint (`/.well-known/jwks.json`) are guaranteed to break on key rotation.

The debugging move that separates senior engineers is checking the `WWW-Authenticate` header immediately rather than reaching for logs first. That header is the server's self-reported diagnosis — `error="expired_token"` or `error="invalid_signature"` gets you to the root cause in seconds. Most junior developers ignore it entirely and spend 20 minutes grepping through application logs.

The adjacent errors you'll find around 401 clusters in production: `ERR_INVALID_REDIRECT` in the browser (the server is redirecting to a login page that the browser follows, ending up at an HTML page when JSON was expected), and CORS errors when the 401 response doesn't include `Access-Control-Allow-Origin` (some auth middleware doesn't add CORS headers on error responses, causing the browser to hide the 401 behind a CORS error, making the client developer think it's a network issue instead of an auth issue).

By Bikram Nath · Curator · Updated June 2026

Frequently asked questions

What is the difference between 401 and 403?

401 means 'you haven't proven who you are yet — please authenticate.' 403 means 'I know who you are, but you don't have permission to do this.' A logged-in user hitting a route they don't have access to gets a 403. An anonymous user hitting any protected route gets a 401.

Why do I get 401 on preflight OPTIONS requests?

Preflight OPTIONS requests are sent by the browser before the actual request. They must not require authentication — the server should return 200 (or 204) on OPTIONS without checking credentials. If your auth middleware runs before CORS middleware, it will reject the preflight with a 401. Fix the middleware order: CORS before auth.

My token is valid in Postman but I get 401 in the browser — why?

The browser is almost certainly not sending the Authorization header or cookie due to CORS restrictions. Check the browser Network tab: look at the actual request headers on the failing call (not the preflight). Missing `credentials: 'include'` in fetch, or missing `Access-Control-Allow-Credentials: true` on the server, are the most common causes.

disclosure:Errordex runs AdSense, has zero third-party affiliate or sponsored links, and occasionally links to the editor’s own paid digital products (clearly labelled). Every fix is cross-referenced against the official sources listed in the “sources” sidebar before it ships. If a fix here didn’t work for you, please email so we can update the page.