When an unauthorized user attempts to access a protected URL or private API endpoint, how should the server respond?

This architectural question pits HTTP 403 Forbidden against HTTP 404 Not Found, balancing transparent debugging against resource enumeration security.

Core distinction: disclosure versus explicit refusal

  • HTTP 403 Forbidden: The server admits that the resource exists, but states: “You are not allowed to see or modify it.”
  • HTTP 404 Not Found: The server pretends the resource does not exist, stating: “Nothing found here.”

Both behaviors are permitted by HTTP specifications, but they serve different security and disclosure objectives.

What HTTP 403 means

An HTTP 403 status indicates that the server understood the request but refuses to authorize or fulfill it.

When an API returns 403, it confirms two pieces of information:

  1. The requested endpoint and resource identifier are valid and exist in the backend.
  2. The authenticated identity lacks sufficient permissions to access it.
HTTP/1.1 403 Forbidden
Content-Type: application/json

{"error": "forbidden", "message": "You do not have access to document doc_private_99."}

What HTTP 404 means

An HTTP 404 status indicates that the origin server did not find a current representation for the target resource.

However, RFC 9110 explicitly states that a server MAY return status 404 if it wishes to hide the existence of a target resource from an unauthorized client.

HTTP/1.1 404 Not Found
Content-Type: application/json

{"error": "not_found", "message": "Resource not found."}

Resource disclosure and access policy

Returning 403 Forbidden leaks information: an attacker scanning paths (e.g., /v1/admin/secret-project) learns that secret-project is a valid internal entity simply because the server responded with 403 instead of 404.

By returning 404 Not Found for both non-existent resources and unauthorized resources:

  • An attacker cannot tell whether an ID or URL slug actually exists.
  • This prevents resource enumeration attacks across multi-tenant SaaS systems.

However, hiding existence using 404 is a disclosure policy choice, not access control. Real authorization checks must still run on the server before deciding to return 404.

Why an API can intentionally use either status

Different applications choose different strategies based on their threat model:

1. Transparent / Developer-Focused (403 Forbidden)

  • Use case: Internal microservices, B2B APIs, and administrative dashboards.
  • Advantage: Eliminates debugging confusion. Developers immediately know whether their token role or scope needs elevation.

2. Privacy-Centric / Multi-Tenant (404 Not Found)

  • Use case: SaaS platforms (e.g., private GitHub repositories, confidential invoices, medical records).
  • Advantage: Prevents outsiders from confirming the existence of sensitive usernames, projects, or documents.

Practical comparison table

Dimension HTTP 403 Forbidden HTTP 404 Not Found (Hidden)
Information Disclosure Confirms resource exists Keeps resource existence private
Enumeration Risk Higher (vulnerable to probing) Lower (indistinguishable from missing)
Debugging Clarity High (clearly signals permission issue) Low (looks like a typo or missing entity)
Best Used For Role-based internal APIs, admin panels Public multi-tenant platforms, private data

What to check before changing code

  1. Review your security disclosure policy: Does your API require hiding resource existence from unauthorized callers?
  2. Never substitute 404 for authorization logic: Always evaluate access tokens and permissions before deciding to return 404.
  3. Ensure consistent error payloads: When using 404 to obscure resources, ensure response time and JSON error structure match authentic 404 responses to avoid timing attacks.

Key takeaway

Use HTTP 403 Forbidden when transparent permission feedback is preferred and disclosing resource existence is safe. Use HTTP 404 Not Found when security policy requires preventing unauthorized users from confirming that a confidential resource exists.