Calcul 100% dans le Navigateur • Zéro Journal Serveur • Exécution Instantanée
Curious TechieBoîte à Outils
HTTP Status Codes7 min✓ Correctif Vérifié en Production • FR

Why a Website Returns 401

Resolve 401 Unauthorized errors: expired JWT tokens, missing Authorization headers, and WWW-Authenticate challenges

Primary Question • Diagnostic Problem

Why a Website Returns 401

A client sends an HTTP request to access a protected resource or API endpoint, and the server replies with status code 401 Unauthorized. The user is either logged out, their session is abruptly interrupted, or an automated integration script fails to authenticate.

Console / Terminal Error OutputExact Message
401 Unauthorized (Response includes: WWW-Authenticate: Bearer error="invalid_token", error_description="The access token expired").
Affected Protocols & Technologies:HTTP/1.1HTTP/2OAuth 2.0JWT Bearer AuthenticationHTTP Basic Auth (RFC 7617)
Secondary Question • Root Cause Mechanics

Why does Why a Website Returns 401 occur?

Primary Root Cause

The server endpoint is protected by an authentication guard (middleware, gateway, or basic auth challenge). When the request arrived, the server checked for an Authorization header or authentication cookie, found none, or detected that the cryptographic signature, expiration timestamp, or session ID was invalid.

HTTP authentication follows a challenge-response pattern defined in RFC 9110 Section 15.5.2. If a client attempts an unauthenticated request to a protected URL, the server returns 401 with a `WWW-Authenticate` header describing the authentication scheme (e.g. `WWW-Authenticate: Bearer realm="api"`). The client is expected to present valid credentials in subsequent requests.

4. Observable Symptoms

How to Confirm You Are Facing This Exact Issue

1

The browser displays a native operating system login popup asking for Username and Password (HTTP Basic Auth).

2

API returns JSON payload: {"error": "unauthorized", "message": "jwt expired"}.

3

The user is suddenly redirected back to the login page mid-workflow.

4

API requests pass locally in Postman or development but consistently fail with 401 in production.

5. Diagnostic Decision Tree (GEO Engine Logic)

Explicit Diagnostic Reasoning

Follow these diagnostic steps to identify why authentication failed:

Condition:

"If 401 occurs after 15 to 60 minutes of active usage"

Probable Cause:

The short-lived JWT access token expired and the frontend failed to execute a silent refresh token rotation.

Remedial Action:

→ Inspect the "exp" claim using a JWT decoder tool and verify the frontend refresh token interceptor.

Condition:

"If 401 occurs on all API requests in production behind an Apache or Nginx reverse proxy"

Probable Cause:

The reverse proxy is stripping the Authorization HTTP header before forwarding requests to the upstream application.

Remedial Action:

→ Add "proxy_set_header Authorization $http_authorization;" in Nginx or "SetEnvIf Authorization" in Apache.

Condition:

"If a native browser modal prompts for username/password"

Probable Cause:

The server returned "WWW-Authenticate: Basic realm=...", triggering the browser’s built-in basic auth dialog.

Remedial Action:

→ Provide valid basic auth credentials or reconfigure server .htpasswd settings.

Condition:

"If the server returns 401 with "WWW-Authenticate: Bearer error=invalid_signature""

Probable Cause:

The JWT was signed with a different secret or private key than the backend validation service expects.

Remedial Action:

→ Verify that the secret string or JWKS endpoint URL matches between the auth server and API gateway.

Verification CommandRun in terminal
curl -I https://api.example.com/protected-data

Inspects the 401 response headers to check the WWW-Authenticate realm and error parameters.

6. Architectural Strategy

Long-Term Engineering Solution

Implement automatic token refresh mechanisms in client HTTP interceptors, ensure correct header propagation across proxies, and validate credential expiration before dispatch.

Architectural Pattern

Pair short-lived access tokens (15 minutes) with secure, HttpOnly refresh tokens (7 days) and implement an automatic Axios/Fetch 401 interceptor that refreshes the token seamlessly without logging the user out.

Secondary Question • Implementation Pipeline

How do you fix this step-by-step?

1

Attach Authorization Header to Outgoing Requests

Ensure the client attaches the Bearer token with correct syntax (word "Bearer" followed by a single space and the token).

javascriptCopy Code
const token = localStorage.getItem('access_token');

const response = await fetch('https://api.example.com/user/profile', {
  headers: {
    // Note space between 'Bearer' and the token
    'Authorization': `Bearer ${token}`,
    'Content-Type': 'application/json',
  },
});
2

Preserve Authorization Header in Reverse Proxy

Configure Nginx or Apache to forward the Authorization header to upstream application servers.

nginxCopy Code
# Nginx reverse proxy configuration
location /api/ {
    proxy_pass http://localhost:3000;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    
    # CRITICAL: Forward Authorization header
    proxy_set_header Authorization $http_authorization;
    proxy_pass_header Authorization;
}
3

Implement Silent Refresh Interceptor

Set up an Axios or fetch response interceptor that catches 401 errors, requests a new access token, and retries the original request.

javascriptCopy Code
import axios from 'axios';

const api = axios.create({ baseURL: 'https://api.example.com' });

api.interceptors.response.use(
  (response) => response,
  async (error) => {
    const originalRequest = error.config;
    // Catch 401 and avoid infinite loops
    if (error.response?.status === 401 && !originalRequest._retry) {
      originalRequest._retry = true;
      try {
        const { data } = await axios.post('/auth/refresh', {}, { withCredentials: true });
        api.defaults.headers.common['Authorization'] = `Bearer ${data.accessToken}`;
        originalRequest.headers['Authorization'] = `Bearer ${data.accessToken}`;
        return api(originalRequest); // Retry original call
      } catch (refreshErr) {
        window.location.href = '/login'; // Refresh failed, redirect to login
      }
    }
    return Promise.reject(error);
  }
);
4

Verify Token Signature and Expiration Locally

Inspect token payload claims to ensure the token has not expired and holds the expected issuer and audience.

bashCopy Code
# Inspect token claims via Curious-Techie JWT Decoder
# Or decode payload in Node.js:
node -e "console.log(JSON.parse(Buffer.from(process.argv[1].split('.')[1], 'base64').toString()))" "<YOUR_JWT_HERE>"
Common Misconceptions & Pitfalls

What are common misconceptions and mistakes when fixing this?

×

Omitting the "Bearer " prefix and sending only the raw JWT string.

Pourquoi cela échoue : Backend frameworks expecting OAuth 2.0 Bearer tokens fail to parse the header and reject it with 401 Malformed Authorization Header.

✓ Alternative correcte : Always format the header as `Authorization: Bearer <token>` with a single space.

×

Returning 401 when the user lacks permission for an admin resource.

Pourquoi cela échoue : If the user is already authenticated, returning 401 causes client apps to attempt a re-login loop. The correct status is 403 Forbidden.

✓ Alternative correcte : Return 401 only when authentication credentials are absent or invalid. Return 403 when the user is authenticated but not authorized.

×

Failing to include the WWW-Authenticate header in 401 responses.

Pourquoi cela échoue : RFC 9110 explicitly mandates that 401 responses must specify the authentication scheme, causing automated API clients to fail compliant handshakes.

✓ Alternative correcte : Always return `WWW-Authenticate: Bearer error="invalid_token"` in 401 API responses.

Threat Analysis & Security Hardening

Can this fix introduce security vulnerabilities?

401 Unauthorized status is the gatekeeper of application privacy, preventing anonymous access to protected data.

Security Risks If Ignored

  • •Credential Stuffing: Endpoints that leak detailed 401 error messages ("Username not found" vs "Wrong password") allow account enumeration.
  • •Token Leakage: Storing JWT tokens insecurely in LocalStorage exposes them to Cross-Site Scripting (XSS) extraction.
  • •Infinite Refresh Loops: Buggy 401 interceptors can trigger denial-of-service hammering against auth servers.

Hardening Best Practices

  • ✓Store refresh tokens in secure, HttpOnly, SameSite=Strict cookies.
  • ✓Return generic authentication failure messages ("Invalid credentials") to prevent username harvesting.
  • ✓Implement rate limiting (e.g. 5 attempts per minute) on endpoints returning 401 to prevent brute-force attacks.
10. Concrete Code Example

Validating Authorization Header in Express.js Backend Middleware

× Broken / Vulnerable Code (javascript)
// BROKEN: Does not check format or return WWW-Authenticate header
app.get('/api/protected', (req, res) => {
  const token = req.headers['authorization'];
  if (!token) {
    return res.status(401).json({ error: 'No token' }); // Non-compliant RFC response
  }
  // Throws unhandled error if token is malformed
  const user = jwt.verify(token, 'secret');
  res.json({ user });
});
Problème : Fails to check Bearer prefix, does not set WWW-Authenticate header, and crashes if the token is formatted improperly.
✓ Fixed / Hardened Code (javascript)
// PRODUCTION COMPLIANT AUTHENTICATION GUARD
app.get('/api/protected', (req, res) => {
  const authHeader = req.headers['authorization'];
  
  if (!authHeader || !authHeader.startsWith('Bearer ')) {
    res.setHeader('WWW-Authenticate', 'Bearer realm="api", error="invalid_request"');
    return res.status(401).json({ error: 'Missing or malformed Authorization header' });
  }

  const token = authHeader.split(' ')[1];
  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    req.user = decoded;
    res.json({ status: 'success', data: req.user });
  } catch (err) {
    const errorType = err.name === 'TokenExpiredError' ? 'expired_token' : 'invalid_token';
    res.setHeader('WWW-Authenticate', `Bearer realm="api", error="${errorType}"`);
    return res.status(401).json({ error: 'Token invalid or expired', code: errorType });
  }
});
Remédiation : Follows RFC 9110 standards, validates the Bearer schema, sets WWW-Authenticate headers, and handles expiration gracefully.
Direct Technical Answers • Knowledge Base

Frequently Asked Questions About Why a Website Returns 401

Why does my API return 401 instead of 403?↓

401 indicates that no valid credentials were submitted or the token expired. 403 means valid credentials were provided, but the account lacks permission for that action.

Think of 401 as a locked front door requiring an ID badge. If you have no badge or your badge is expired, you get 401. If you enter the building but try to walk into the CEO’s private office, your badge is rejected with 403 Forbidden.

Can 401 be triggered by CORS preflight issues?↓

Yes. If your backend authentication middleware intercepts HTTP OPTIONS preflight requests and rejects them because they lack a Bearer token, the browser blocks the call with a CORS/401 error.

CORS preflight (OPTIONS) requests never include authorization headers or cookies by browser security design. If backend middleware requires auth on all routes including OPTIONS, preflights will receive 401 and fail immediately.

How should frontend applications handle 401 responses?↓

Attempt a single silent token refresh call. If the refresh succeeds, retry the original request. If the refresh fails, clear auth state and redirect the user to login.

Modern single-page applications use HTTP interceptors to handle 401 errors seamlessly. This ensures users do not lose form input or get violently logged out when an access token reaches its 15-minute expiration limit.