403 Forbidden

The server understood the request and knows who you are, but refuses to authorize access to the resource.

Quick Reference

Category4xx Client Error
RFCRFC 9110, Section 15.5.4
CacheableYes — 403 is cacheable by default
RetryableNo — the same request will always be refused
Key distinction403 = authenticated but unauthorized; 401 = not authenticated

What 403 Forbidden Means

HTTP 403 Forbidden tells the client that the server understood the request but refuses to fulfill it. Unlike 401 Unauthorized — which means the client has not authenticated yet — 403 means the server knows the identity of the requester and has decided that identity does not have permission to perform the requested action.

RFC 9110 states that a 403 response indicates that “the server understood the request but refuses to authorize it.” Critically, sending credentials again will not help: the current credentials are valid, they just lack the necessary permissions. Re-authentication will not change the outcome.

The practical distinction: if you are not logged in and try to access an admin page, you get 401 (please authenticate). If you are logged in as a regular user and try to access an admin page, you get 403 (you are authenticated, but you are not an admin).

Common Causes of 403

Insufficient Permissions

The most common cause. The authenticated user has a role or set of permissions that does not include the requested action. An API that enforces role-based access control (RBAC) returns 403 when a user with a “viewer” role tries to perform a “write” operation:

DELETE /api/posts/42 HTTP/1.1
Authorization: Bearer viewer_token

HTTP/1.1 403 Forbidden
Content-Type: application/json

{"error": "forbidden", "message": "Delete requires editor role or higher"}

IP or Network Restrictions

Servers may restrict access to certain endpoints by IP address, country, or network range. A valid API key from a disallowed IP receives 403. Web application firewalls (WAFs) often return 403 for blocked IPs or flagged request patterns.

Resource Ownership

Multi-tenant applications return 403 when one user tries to access another user’s resource. The user is authenticated, but the resource does not belong to them:

GET /api/users/99/orders HTTP/1.1
Authorization: Bearer user_7_token

HTTP/1.1 403 Forbidden
{"error": "forbidden", "message": "You can only access your own orders"}

API Scope Restrictions

OAuth 2.0 access tokens carry scopes. A token with read:profile scope cannot access an endpoint that requires write:orders scope. The token is valid and the user is authenticated; the token just lacks the required scope.

Missing Two-Factor Authentication

Some applications require MFA for specific operations (deleting an account, viewing billing, changing passwords). A user who is authenticated but has not completed MFA for the session may receive 403 on these sensitive endpoints.

File System Permissions

Web servers return 403 for files or directories where the server process lacks read permission. This is common when deploying files with incorrect Unix permissions (chmod). A file owned by root with 600 permissions is inaccessible to a web server running as www-data.

When to Use 403 vs 404

A common security consideration: should a server return 403 when an authenticated user requests a resource that exists but they do not have access to, or 404 to conceal the resource’s existence?

403 reveals that the resource exists but the requester is forbidden. This can leak information: an attacker who gets 403 knows the resource exists and can try to escalate privileges to access it. 404 does not reveal whether the resource exists at all.

RFC 9110 notes that a server may return 404 instead of 403 if it wants to hide the existence of a resource from an unauthorized client. This is explicitly permitted by the spec. The right choice depends on the sensitivity of the resource’s existence itself. For most applications, 403 is fine. For highly sensitive resources (user data, financial records), 404 is a reasonable security choice.

Returning 403 in Common Frameworks

Express.js with Middleware

function requireRole(role) {
  return (req, res, next) => {
    if (!req.user) {
      return res.status(401).json({ error: 'authentication required' });
    }
    if (!req.user.roles.includes(role)) {
      return res.status(403).json({
        error: 'forbidden',
        message: `Requires ${role} role`
      });
    }
    next();
  };
}

app.delete('/api/posts/:id', requireRole('editor'), async (req, res) => {
  await Post.findByIdAndDelete(req.params.id);
  res.sendStatus(204);
});

Django REST Framework

from rest_framework.permissions import BasePermission
from rest_framework.exceptions import PermissionDenied

class IsEditor(BasePermission):
    def has_permission(self, request, view):
        return request.user.groups.filter(name='editor').exists()

class PostViewSet(ModelViewSet):
    permission_classes = [IsAuthenticated, IsEditor]
    # DRF raises 403 automatically when permission check fails

403 vs Related Status Codes

CodeAuthentication stateCan credentials fix it?
401 UnauthorizedNot authenticatedYes — provide credentials
403 ForbiddenAuthenticated, insufficient permissionsNo — need higher-privilege credentials
404 Not FoundAnyN/A — resource does not exist (or is hidden)
405 Method Not AllowedAnyNo — use a different HTTP method

Frequently Asked Questions

My server returns 403 for a file request — what does that mean?

For static file requests, 403 almost always means a file permissions or directory configuration problem. Check that the web server process can read the file (ls -la on Linux), that the directory does not deny access in the server config (e.g., nginx deny all block or Apache Options -Indexes), and that no parent directory has restrictive permissions.

Should the 403 response body explain why access was denied?

It depends on the audience. For an API consumed by developers, a clear error message helps: "requires admin role" or "this resource belongs to another user". For a public-facing web app where the error might expose internal role or permission structure, keep the message generic. Never expose internal system details in 403 messages.

Is 403 the right code for a banned user?

Yes. A banned user is authenticated (the server knows who they are) but their account is suspended. 403 is appropriate. Include a message in the response body explaining the ban so the user understands they are not experiencing a technical error.

Is 403 cacheable?

By default, yes. A cached 403 response can be served to subsequent requests for the same URL. This is usually not desirable for permission-based responses, since permissions can change. Add Cache-Control: no-store to 403 responses from dynamic authorization checks to prevent caching.