The server understood your request and knows who you are, but you do not have permission to access this resource. Unlike 401, re-authenticating will not help because the issue is authorization, not authentication.
HTTP 403 Forbidden is a client error status code indicating the server understood the request but refuses to authorize it. This is fundamentally different from 401 Unauthorized, which means the client has not been authenticated. With 403, the server knows who the client is but has determined they do not have sufficient permissions to access the requested resource. Re-authenticating or providing different credentials will not resolve a 403 error.
The 403 status code is central to authorization systems in modern web applications. It appears when a user tries to access admin-only endpoints without admin privileges, when a resource has access control lists that exclude the requesting user, when IP-based restrictions block the client's address, or when CORS policies prevent cross-origin requests. Unlike 404, which hides the existence of a resource, 403 explicitly confirms the resource exists but the client cannot access it.
Some security-conscious APIs deliberately return 404 instead of 403 to avoid revealing whether a resource exists. This practice, sometimes called "leaking information through status codes," prevents attackers from probing for valid resource IDs. However, for internal APIs and authenticated endpoints, returning 403 with a clear error message is generally preferred because it helps legitimate developers debug permission issues without ambiguity.
The authenticated user does not have the required role or permission level to access the resource. For example, a regular user trying to access an admin-only endpoint.
The server restricts access to certain IP addresses or ranges. Your request originates from an IP that is not on the allowlist, even though your credentials are valid.
The browser blocked the request because the server's CORS policy does not allow requests from your domain. The server returns 403 for cross-origin requests from unauthorized origins.
The user is authenticated and has general access, but the specific resource has access controls that exclude this user. For example, trying to view another user's private document.
Verify that the authenticated user has the required role or permission to access the resource. Check your authorization system's role assignments and permission mappings.
If the 403 is CORS-related, check the server's Access-Control-Allow-Origin header. Ensure your frontend domain is included in the allowed origins list.
If the API restricts access by IP address, check that your client's IP is on the allowlist. This is common for production APIs, webhook endpoints, and admin panels.
If the user is not authenticated at all, return 401 instead. Reserve 403 for cases where the user is authenticated but lacks the necessary permissions to access the specific resource.
// Express.js — role-based access control returning 403
const express = require('express');
const app = express();
// Role-based authorization middleware
function requireRole(...roles) {
return (req, res, next) => {
// req.user is set by authentication middleware
if (!req.user) {
return res.status(401).json({ error: 'Authentication required' });
}
if (!roles.includes(req.user.role)) {
return res.status(403).json({
error: 'Forbidden',
message: `Required role: ${roles.join(' or ')}. Your role: ${req.user.role}`
});
}
next();
};
}
// Only admins can delete users
app.delete('/api/users/:id', authenticate, requireRole('admin'), async (req, res) => {
await User.destroy({ where: { id: req.params.id } });
res.status(204).end();
});
// Resource-level access control
app.get('/api/documents/:id', authenticate, async (req, res) => {
const doc = await Document.findById(req.params.id);
if (!doc) return res.status(404).json({ error: 'Not found' });
if (doc.ownerId !== req.user.id && !doc.sharedWith.includes(req.user.id)) {
return res.status(403).json({
error: 'Forbidden',
message: 'You do not have access to this document'
});
}
res.json(doc);
});
# Flask — role-based access control returning 403
from flask import Flask, jsonify, request, g
from functools import wraps
app = Flask(__name__)
def require_role(*roles):
def decorator(f):
@wraps(f)
def decorated(*args, **kwargs):
if not hasattr(g, 'user') or g.user is None:
return jsonify(error='Authentication required'), 401
if g.user['role'] not in roles:
return jsonify(
error='Forbidden',
message=f'Required: {" or ".join(roles)}. Your role: {g.user["role"]}'
), 403
return f(*args, **kwargs)
return decorated
return decorator
# Only admins can delete users
@app.route('/api/users/<int:user_id>', methods=['DELETE'])
@require_auth
@require_role('admin')
def delete_user(user_id):
User.query.get_or_404(user_id).delete()
db.session.commit()
return '', 204
# Resource-level access control
@app.route('/api/documents/<int:doc_id>', methods=['GET'])
@require_auth
def get_document(doc_id):
doc = Document.query.get_or_404(doc_id)
if doc.owner_id != g.user['id'] and g.user['id'] not in doc.shared_with:
return jsonify(
error='Forbidden',
message='You do not have access to this document'
), 403
return jsonify(doc.to_dict())
HTTP 401 means the client is not authenticated and should provide credentials. HTTP 403 means the client is authenticated but does not have permission to access the resource. Fixing a 401 requires logging in. Fixing a 403 requires getting the right permissions.
If revealing the existence of a resource is a security concern, return 404 to hide it. If the resource is not sensitive and transparency helps developers, return 403 with a clear error message. Many public APIs use 403 for authorization failures.
Yes. If the server's CORS policy does not include the requesting origin in Access-Control-Allow-Origin, the browser may show a 403 error. Configure your server to allow the correct origins for cross-domain requests.
File permissions at the OS level, web server configuration, or application-level ACLs may prevent access even for the file owner. Check file system permissions, .htaccess rules, and your application's authorization logic.
Yes. If Googlebot receives a 403, it cannot crawl the page and will not index it. Ensure public-facing pages return 200 for search engine crawlers. Use robots.txt to block pages you do not want indexed rather than returning 403.
Get instant alerts when your endpoints go down. 60-second checks, free forever.
Start Monitoring Free →