The server requires authentication to access this resource, and your request either did not include credentials or the credentials you provided are invalid. You need to authenticate with valid credentials before the server will grant access.
HTTP 401 Unauthorized is a client error status code indicating that the request lacks valid authentication credentials for the target resource. Despite its name containing "Unauthorized," this status code is actually about authentication, not authorization. The server is saying "I do not know who you are" rather than "I know who you are but you are not allowed." The distinction between authentication (who are you?) and authorization (what can you do?) is an important concept that HTTP 401 and 403 address separately.
When a server returns 401, it must include a WWW-Authenticate header that describes the authentication scheme the client should use. Common schemes include Bearer for JWT and OAuth tokens, Basic for username and password credentials, and API-Key for key-based authentication. This header helps clients understand what type of credentials they need to provide in order to access the resource.
In modern web development, 401 errors are most commonly triggered by expired JWT tokens, missing API keys, revoked OAuth access tokens, or malformed Authorization headers. Understanding the difference between 401 and 403 is critical for building secure APIs. A 401 response invites the client to authenticate, while a 403 tells an authenticated client that they simply do not have permission, and re-authenticating will not help. Properly distinguishing these codes makes your API self-documenting and easier to integrate.
The request does not include an Authorization header. The API requires a Bearer token, API key, or Basic auth credentials that were not sent with the request.
The JWT or OAuth access token included in the request has expired. Tokens typically have a limited lifetime, and the server rejects expired tokens with a 401 response.
The API key in the request is incorrect, revoked, or belongs to a different environment. Development and production environments often use separate API keys.
The Authorization header is present but formatted incorrectly. For example, missing the Bearer prefix before the token, or including extra whitespace characters.
Include the required Authorization header in your request. For Bearer tokens, use the format Authorization: Bearer <token>. For Basic auth, use Authorization: Basic <base64-encoded-credentials>.
If using JWT or OAuth, implement token refresh logic. When you receive a 401, use the refresh token to obtain a new access token and retry the original request automatically.
Ensure you are using the correct API key for your environment. Production, staging, and development environments typically have separate keys that are not interchangeable.
Verify the Authorization header format matches the API requirements. Use the JWT Decoder to inspect your token structure, expiration time, and claims.
// Express.js — JWT authentication middleware
const express = require('express');
const jwt = require('jsonwebtoken');
const app = express();
function authenticate(req, res, next) {
const authHeader = req.headers.authorization;
if (!authHeader) {
return res.status(401).json({
error: 'Unauthorized',
message: 'Authorization header is required'
});
}
const [scheme, token] = authHeader.split(' ');
if (scheme !== 'Bearer' || !token) {
return res.status(401).json({
error: 'Unauthorized',
message: 'Invalid authorization format. Use: Bearer <token>'
});
}
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;
next();
} catch (err) {
const message = err.name === 'TokenExpiredError'
? 'Token has expired' : 'Invalid token';
return res.status(401).json({ error: 'Unauthorized', message });
}
}
app.get('/api/profile', authenticate, (req, res) => {
res.json({ userId: req.user.id, email: req.user.email });
});
# Flask — JWT authentication decorator
from flask import Flask, jsonify, request
from functools import wraps
import jwt
import os
app = Flask(__name__)
def require_auth(f):
@wraps(f)
def decorated(*args, **kwargs):
auth_header = request.headers.get('Authorization')
if not auth_header:
return jsonify(
error='Unauthorized',
message='Authorization header is required'
), 401
parts = auth_header.split()
if len(parts) != 2 or parts[0] != 'Bearer':
return jsonify(
error='Unauthorized',
message='Invalid format. Use: Bearer <token>'
), 401
try:
payload = jwt.decode(
parts[1],
os.environ['JWT_SECRET'],
algorithms=['HS256']
)
request.user = payload
except jwt.ExpiredSignatureError:
return jsonify(error='Unauthorized', message='Token expired'), 401
except jwt.InvalidTokenError:
return jsonify(error='Unauthorized', message='Invalid token'), 401
return f(*args, **kwargs)
return decorated
@app.route('/api/profile', methods=['GET'])
@require_auth
def get_profile():
return jsonify(user_id=request.user['id'], email=request.user['email'])
HTTP 401 means the client is not authenticated. The server does not know who you are. HTTP 403 means the client is authenticated but does not have permission to access the resource. Re-authenticating will fix a 401 but not a 403.
This is a well-known misnomer in the HTTP specification. HTTP 401 is actually about authentication, not authorization. The naming is confusing but has been in the spec since HTTP/1.0 and cannot be changed for backward compatibility.
Implement a token refresh flow. When you receive a 401, check if you have a refresh token. If so, call the token refresh endpoint to get a new access token and retry the original request. If the refresh also fails, redirect the user to the login page.
Return 401 for an invalid, expired, or missing API key because the client has not authenticated successfully. Return 403 when the API key is valid but does not have permission for the specific resource or action.
Yes. Include an error message in the body explaining why authentication failed, such as missing header, expired token, or invalid credentials. Also include the WWW-Authenticate header to indicate the expected authentication scheme.
Get instant alerts when your endpoints go down. 60-second checks, free forever.
Start Monitoring Free →