Outrank
Outrank writes its own blog posts. Yes, you heard that right!
Table of Contents
- 401 vs 403 HTTP Status Codes: Complete Guide with Examples & Fixes
- 1. Status Code Purpose and Definition
- 401 Unauthorized: Official Definition
- 403 Forbidden: Official Definition
- 2. Authentication vs Authorization Distinction
- Authentication: Verifying Identity
- Authorization: Verifying Permissions
- The Authentication and Authorization Flow
- 3. Header Requirements and Response Handling
- 401 Unauthorized Headers
- 403 Forbidden Headers
- Client-Side Handling Differences
- 4. Implementation in Web Frameworks and APIs
- Node.js/Express
- Python/Django
- Java/Spring Security
- 5. Security Implications and Best Practices
- Preventing Information Disclosure
- Consistent Error Messages
- Rate Limiting Authentication Attempts
- OWASP Recommendations
- 6. Troubleshooting Guide & Common Fixes
- Diagnosing 401 Unauthorized Errors
- Diagnosing 403 Forbidden Errors
- 7. Decision Making: When to Use Each Code
- Edge Cases and Special Considerations
- Conclusion
- Implementation Checklist
- Related HTTP Status Codes
- Further Resources

Do not index
Do not index
401 vs 403 HTTP Status Codes: Complete Guide with Examples & Fixes
When securing web applications and APIs, understanding the difference between 401 Unauthorized and 403 Forbidden status codes is critical for implementing proper authentication and authorization flows. These status codes are often confused but serve distinct purposes in HTTP security.
Quick Reference Table:
Aspect | 401 Unauthorized | 403 Forbidden |
Purpose | Authentication failure | Authorization failure |
Meaning | "You need to authenticate" | "You're authenticated but not authorized" |
Required Header | WWW-Authenticate | None |
Common Cause | Missing or invalid credentials | Insufficient permissions |
Client Action | Provide valid credentials | Request access rights |
1. Status Code Purpose and Definition
401 Unauthorized: Official Definition
According to RFC 7235 (HTTP Authentication), a 401 Unauthorized status code indicates:
"The request has not been applied because it lacks valid authentication credentials for the target resource."
The 401 status code specifically addresses authentication - the process of verifying who a user is. When a server returns a 401, it's saying: "I don't know who you are, or I can't verify your identity."
A proper 401 response must include a
WWW-Authenticate
header that specifies the authentication scheme(s) and parameters applicable to the target resource.Example 401 HTTP response:
HTTP/1.1 401 Unauthorized
Date: Tue, 20 May 2025 12:30:24 GMT
WWW-Authenticate: Basic realm="Access to the staging site"
Content-Type: text/html; charset=iso-8859-1
Content-Length: 311
<!DOCTYPE HTML>
<html>
<head>
<title>Error 401: Unauthorized</title>
</head>
<body>
<h1>Unauthorized</h1>
<p>You are not authorized to access this resource. Please provide valid credentials.</p>
</body>
</html>
This response tells the client that it needs to authenticate using HTTP Basic Authentication.
403 Forbidden: Official Definition
According to RFC 7231 (HTTP Semantics), a 403 Forbidden status code indicates:
"The server understood the request but refuses to authorize it."
The 403 status code addresses authorization - the process of determining what permissions an authenticated user has. When a server returns a 403, it's saying: "I know who you are, but you don't have permission to access this resource."
Unlike 401, a 403 response does not require any specific headers, as the issue is not about authentication methods but about access rights.
Example 403 HTTP response:
HTTP/1.1 403 Forbidden
Date: Tue, 20 May 2025 12:30:24 GMT
Content-Type: text/html; charset=iso-8859-1
Content-Length: 289
<!DOCTYPE HTML>
<html>
<head>
<title>Error 403: Forbidden</title>
</head>
<body>
<h1>Forbidden</h1>
<p>You don't have permission to access this resource.</p>
</body>
</html>
This response clearly indicates that while the server understood the request, it's refusing to fulfill it due to insufficient permissions.
2. Authentication vs Authorization Distinction
Understanding the difference between authentication and authorization is fundamental to correctly implementing 401 and 403 status codes.
Authentication: Verifying Identity
Authentication is the process of verifying who a user is. It answers the question: "Who are you?"
Common authentication methods include:
- Username and password
- API keys
- OAuth tokens
- JWT (JSON Web Tokens)
- Digital certificates
- Biometric verification
When authentication fails (missing or invalid credentials), the server should return a 401 Unauthorized status code.
Authorization: Verifying Permissions
Authorization is the process of verifying what a user has permission to do. It answers the question: "What are you allowed to access?"
Authorization typically occurs after successful authentication and may involve:
- Role-based access control (RBAC)
- Attribute-based access control (ABAC)
- Access control lists (ACLs)
- Permission sets
- Scope-based access (common in OAuth)
When authorization fails (insufficient permissions), the server should return a 403 Forbidden status code.
The Authentication and Authorization Flow
Here's how these processes typically work together:
- Client makes a request to a protected resource
- Server checks for authentication credentials
- If missing/invalid → 401 Unauthorized
- If valid → proceeds to authorization check
- Server checks if the authenticated user has permission
- If insufficient permissions → 403 Forbidden
- If authorized → 200 OK with requested resource
This flow determines which status code should be returned in different scenarios.
3. Header Requirements and Response Handling
The headers associated with 401 and 403 responses are critical for proper client behavior and security implementation.
401 Unauthorized Headers
A 401 response must include the
WWW-Authenticate
header, which tells the client how to authenticate. This header typically contains:- The authentication scheme (Basic, Bearer, Digest, etc.)
- Additional parameters specific to that scheme
Examples of
WWW-Authenticate
headers:Basic Authentication:
WWW-Authenticate: Basic realm="Company Portal"
Bearer Token (OAuth):
WWW-Authenticate: Bearer realm="api.example.com", error="invalid_token", error_description="The access token has expired"
Digest Authentication:
WWW-Authenticate: Digest realm="api.example.com", qop="auth,auth-int", nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093", opaque="5ccc069c403ebaf9f0171e9517f40e41"
The browser or API client will typically respond to this header by prompting for credentials or refreshing the authentication token.
403 Forbidden Headers
A 403 response has no mandatory headers. However, for better user experience, you might include:
X-Reason
or similar custom header to explain the reason for access denial
- Standard
Content-Type
and other HTTP headers
- CORS headers if applicable
Example of a 403 response with optional explanatory headers:
HTTP/1.1 403 Forbidden
Date: Tue, 20 May 2025 12:30:24 GMT
Content-Type: application/json
X-Reason: insufficient-permissions
{
"error": "forbidden",
"message": "You don't have the required permissions to access this resource",
"required_role": "admin"
}
Client-Side Handling Differences
Different clients handle 401 and 403 responses differently:
Web Browsers:
- For 401 with Basic auth: Display built-in login prompt
- For 401 with other schemes: No built-in handling
- For 403: Display error page, no authentication prompt
API Clients:
- For 401: Typically attempt to refresh tokens or request new credentials
- For 403: Report permission error to the application
Single-Page Applications (SPAs):
- For 401: Often redirect to login page or show login modal
- For 403: Display permission error or request elevation
Proper implementation must consider these client behaviors.
4. Implementation in Web Frameworks and APIs
Let's look at how to correctly implement 401 and 403 status codes in various frameworks.
Node.js/Express
401 Unauthorized in Express:
// Authentication middleware
const authMiddleware = (req, res, next) => {
const authHeader = req.headers.authorization;
if (!authHeader) {
// No credentials provided
res.setHeader('WWW-Authenticate', 'Bearer realm="api.example.com"');
return res.status(401).json({
error: 'unauthorized',
message: 'Authentication required'
});
}
// Basic token validation (example only)
const token = authHeader.split(' ')[1];
try {
const decoded = verifyToken(token);
req.user = decoded;
next();
} catch (error) {
// Invalid token
res.setHeader('WWW-Authenticate', 'Bearer realm="api.example.com", error="invalid_token"');
return res.status(401).json({
error: 'unauthorized',
message: 'Invalid or expired token'
});
}
};
app.get('/api/protected', authMiddleware, (req, res) => {
// Handler for authenticated requests
res.json({ data: 'Protected data' });
});
403 Forbidden in Express:
// Authorization middleware
const requireAdmin = (req, res, next) => {
// Assume user is attached by previous auth middleware
if (!req.user || req.user.role !== 'admin') {
return res.status(403).json({
error: 'forbidden',
message: 'Administrator access required'
});
}
next();
};
app.get('/api/admin/settings', authMiddleware, requireAdmin, (req, res) => {
// Handler for admin-only endpoint
res.json({ settings: 'Admin settings' });
});
Python/Django
401 Unauthorized in Django:
from django.http import JsonResponse
def authenticate_user(request):
auth_header = request.META.get('HTTP_AUTHORIZATION')
if not auth_header:
response = JsonResponse({
'error': 'unauthorized',
'message': 'Authentication required'
}, status=401)
response['WWW-Authenticate'] = 'Bearer realm="api.example.com"'
return None, response
try:
# Basic token validation (example only)
token = auth_header.split(' ')[1]
user = verify_token(token)
return user, None
except Exception:
response = JsonResponse({
'error': 'unauthorized',
'message': 'Invalid or expired token'
}, status=401)
response['WWW-Authenticate'] = 'Bearer realm="api.example.com", error="invalid_token"'
return None, response
def protected_view(request):
user, response = authenticate_user(request)
if response:
return response
# User is authenticated
return JsonResponse({'data': 'Protected data'})
403 Forbidden in Django:
def admin_required(view_func):
def wrapper(request, *args, **kwargs):
user, response = authenticate_user(request)
if response:
return response
if not user.is_admin:
return JsonResponse({
'error': 'forbidden',
'message': 'Administrator access required'
}, status=403)
return view_func(request, *args, **kwargs)
return wrapper
@admin_required
def admin_settings(request):
return JsonResponse({'settings': 'Admin settings'})
Java/Spring Security
401 and 403 in Spring Security:
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
// Public endpoints
.antMatchers("/api/public/**").permitAll()
// Protected endpoints requiring authentication
.antMatchers("/api/users/**").authenticated()
// Admin endpoints requiring ADMIN role
.antMatchers("/api/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
.and()
.exceptionHandling()
// Handle 401 Unauthorized
.authenticationEntryPoint((request, response, authException) -> {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setHeader("WWW-Authenticate", "Bearer realm=\"api.example.com\"");
response.setContentType("application/json");
response.getWriter().write(
"{\"error\":\"unauthorized\",\"message\":\"Authentication required\"}"
);
})
// Handle 403 Forbidden
.accessDeniedHandler((request, response, accessDeniedException) -> {
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
response.setContentType("application/json");
response.getWriter().write(
"{\"error\":\"forbidden\",\"message\":\"You don't have permission to access this resource\"}"
);
});
}
}
5. Security Implications and Best Practices
When implementing 401 and 403 status codes, consider these security best practices:
Preventing Information Disclosure
Security Consideration: Returning different status codes can inadvertently reveal the existence of resources.
Best Practice: In highly sensitive applications, consider using 404 Not Found instead of 403 Forbidden for unauthorized requests to sensitive resources. This prevents attackers from determining whether a resource exists.
// Instead of 403 for sensitive resources
if (!isAuthorized(user, resourceId)) {
return res.status(404).json({
error: 'not_found',
message: 'The requested resource was not found'
});
}
Consistent Error Messages
Security Consideration: Overly specific error messages can reveal too much about your security model.
Best Practice: Use generic error messages in responses while logging detailed information server-side.
// Good - Generic public message, detailed logging
if (!isAuthorized(user, resourceId)) {
logger.info(`User ${user.id} attempted to access restricted resource ${resourceId} with insufficient permissions (requires: admin, has: user)`);
return res.status(403).json({
error: 'forbidden',
message: 'Insufficient permissions to access this resource'
});
}
Rate Limiting Authentication Attempts
Security Consideration: Brute force attacks on authentication endpoints.
Best Practice: Implement progressive rate limiting for failed authentication attempts:
// Rate limiting middleware example
const rateLimitLogin = (req, res, next) => {
const ip = req.ip;
const username = req.body.username;
const attempts = getRecentAttempts(ip, username);
if (attempts > 5) {
const timeout = Math.pow(2, attempts - 5) * 1000; // Exponential backoff
logger.warn(`Rate limit exceeded: IP=${ip}, username=${username}, attempts=${attempts}`);
return res.status(403).json({
error: 'forbidden',
message: 'Too many login attempts. Please try again later.',
retry_after: timeout / 1000
});
}
next();
};
OWASP Recommendations
The Open Web Application Security Project (OWASP) provides additional recommendations for secure authentication and authorization:
- Use Multi-Factor Authentication (MFA) where appropriate
- Implement proper session management with secure cookies and token handling
- Follow the principle of least privilege when assigning permissions
- Conduct regular security audits of authentication and authorization mechanisms
- Implement proper CORS policies to prevent unauthorized cross-origin requests
For more security guidance, check out Outrank's Cybersecurity SEO Guide.
6. Troubleshooting Guide & Common Fixes
Diagnosing 401 Unauthorized Errors
Common Causes:
- Missing authentication credentials
- Expired tokens or sessions
- Invalid API keys
- Incorrect authentication headers
- Misconfigured server authentication settings
Troubleshooting Steps:
- Verify the request includes authentication credentials:
# Check if your request includes the Authorization header
curl -v https://api.example.com/protected
- Ensure the correct authentication scheme is used:
# For Basic Auth
curl -v -u username:password https://api.example.com/protected
# For Bearer/OAuth tokens
curl -v -H "Authorization: Bearer your_token_here" https://api.example.com/protected
- Check token expiration:
// Decode JWT without verification
const decoded = jwt_decode(token);
console.log('Expires at:', new Date(decoded.exp * 1000).toISOString());
Common Fixes:
- Add missing authentication headers:
// In JavaScript fetch API
fetch('https://api.example.com/protected', {
headers: {
'Authorization': 'Bearer ' + token
}
});
- Refresh expired tokens:
// Example token refresh flow
async function getValidToken() {
if (isTokenExpired(currentToken)) {
const response = await fetch('https://auth.example.com/oauth/token', {
method: 'POST',
body: new URLSearchParams({
'grant_type': 'refresh_token',
'refresh_token': refreshToken
})
});
const data = await response.json();
currentToken = data.access_token;
}
return currentToken;
}
Diagnosing 403 Forbidden Errors
Common Causes:
- Insufficient user permissions
- Role-based access control (RBAC) restrictions
- IP or geo-location restrictions
- Resource ownership issues
- Rate limiting or quota exceeded
Troubleshooting Steps:
- Verify user permissions:
# Get current user info including roles/permissions
curl -v -H "Authorization: Bearer your_token_here" https://api.example.com/me
- Check if rate limiting is the issue:
# Look for rate limit headers in the response
curl -v https://api.example.com/resource
# Check for headers like X-RateLimit-Limit, X-RateLimit-Remaining, etc.
Common Fixes:
- Implement proper role checks:
// Client-side role check before making requests
function canAccessAdminSection(user) {
return user.roles.includes('admin') || user.permissions.includes('admin:read');
}
if (canAccessAdminSection(currentUser)) {
// Show admin UI
} else {
// Show permission error or request access UI
}
- Handle rate limits appropriately:
// Implement retry with exponential backoff
async function fetchWithRetry(url, options, maxRetries = 3) {
let retries = 0;
while (retries < maxRetries) {
try {
const response = await fetch(url, options);
if (response.status === 403) {
const retryAfter = response.headers.get('Retry-After');
if (retryAfter) {
const waitTime = parseInt(retryAfter) * 1000;
await new Promise(resolve => setTimeout(resolve, waitTime));
retries++;
continue;
}
}
return response;
} catch (error) {
retries++;
if (retries >= maxRetries) throw error;
await new Promise(resolve => setTimeout(resolve, 1000 * Math.pow(2, retries)));
}
}
}
7. Decision Making: When to Use Each Code
When designing APIs or web applications, follow this decision tree to determine whether to use 401 or 403:
- Is the resource protected by authentication?
- No: Use 200 OK for public resources
- Yes: Continue to next question
- Does the request include authentication credentials?
- No: Return 401 Unauthorized
- Yes: Continue to next question
- Are the authentication credentials valid?
- No: Return 401 Unauthorized
- Yes: Continue to next question
- Does the authenticated user have permission to access this resource?
- No: Return 403 Forbidden
- Yes: Return 200 OK with the requested resource
Edge Cases and Special Considerations
1. Protecting Sensitive Resource Existence
When protecting highly sensitive resources, consider using 404 Not Found instead of 403 Forbidden to avoid confirming the existence of a resource to unauthorized users.
2. Multi-Tenant Applications
In multi-tenant applications where resources belong to different organizations, use 403 Forbidden when a user attempts to access resources belonging to a different tenant.
3. API Design Best Practices
For clearer API responses, consider implementing a detailed error response format:
{
"status": 403,
"code": "insufficient_permissions",
"message": "You don't have the required permissions to access this resource",
"details": {
"required_role": "admin",
"current_role": "user",
"resource": "settings",
"action": "update"
},
"help_url": "https://example.com/api/docs/errors/insufficient_permissions"
}
Conclusion
Understanding the distinction between 401 Unauthorized and 403 Forbidden status codes is essential for implementing secure, user-friendly web applications and APIs. The key differences are:
- 401 Unauthorized indicates an authentication failure - the user's identity is unknown or unverified.
- 403 Forbidden indicates an authorization failure - the user is authenticated but lacks sufficient permissions.
- 401 responses require the
WWW-Authenticate
header, while 403 responses don't.
- 401 suggests the request might succeed if authenticated, while 403 indicates authentication won't help.
- Different client behaviors are triggered by these status codes, affecting user experience.
By correctly implementing these status codes, you ensure clear communication between servers and clients, enable proper security measures, and provide a better experience for your users.
Implementation Checklist
✅ Use 401 when authentication is missing or invalid
✅ Include the
WWW-Authenticate
header with all 401 responses✅ Use 403 when a user is authenticated but lacks permissions
✅ Provide clear error messages explaining why access was denied
✅ Implement appropriate security headers
✅ Consider information disclosure implications
✅ Add proper logging for authentication and authorization failures
✅ Test with various authentication states and permission levels
Related HTTP Status Codes
- 400 Bad Request: The request is malformed (often used for invalid input validation)
- 404 Not Found: The requested resource does not exist
- 407 Proxy Authentication Required: Similar to 401, but for proxy servers
- 429 Too Many Requests: Used for rate limiting
Further Resources
Understanding the correct use of HTTP status codes is just one aspect of building secure, robust web applications. By mastering the subtle but important differences between 401 and 403 status codes, you're taking a significant step toward better security implementation and user experience.
Ready to further optimize your website's technical SEO and security? Explore Outrank's content optimization tools to create high-quality, technical content that ranks well and provides value to your users.
Written by