HTTP 403 Forbidden

Quick answer

HTTP 403 Forbidden means the client is authenticated but does not have permission to access the resource. Unlike a 401, sending valid credentials will not help.

What HTTP 403 means

403 Forbidden is about authorization: the server knows who you are but refuses the request. The credentials are valid, but the account lacks the role, scope, or ownership required. Re-authenticating does not help because the problem is permissions, not identity.

Common causes

Example JSON error response

{
  "error": "Forbidden",
  "message": "You do not have permission to access this resource",
  "status": 403
}

Raw HTTP response

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

How to troubleshoot HTTP 403

401 vs 403 — what's the difference?

401 Unauthorized means "who are you?" — authenticate first. 403 Forbidden means "I know who you are, but you can't do this." With a 401, valid credentials fix the problem; with a 403, the credentials are already valid but lack the necessary permission, role, or scope.

403 vs 404 — when to hide a resource's existence

There's a deliberate security trade-off here. A strict 403 tells the caller "this resource exists, but you can't have it" — which confirms the resource exists. For sensitive resources, some APIs intentionally return 404 Not Found instead of 403, so an unauthorized user can't even tell whether the resource is there (e.g. GitHub does this for private repositories). Use 403 when revealing existence is harmless; use 404 when the existence itself is sensitive.

Handling 403 in client code

Unlike a 401, a 403 should not trigger a token refresh or a re-login — the credentials are already valid, so refreshing changes nothing. Treat it as a permanent "no" for this user and show an appropriate message rather than retrying:

const res = await fetch('/api/admin/reports', withAuth());

if (res.status === 401) {
  await refreshAndRetry();          // authentication problem — fixable
} else if (res.status === 403) {
  showMessage("You don't have access to this.");  // do NOT refresh/retry
}

Returning 403 in different frameworks

FrameworkHow to return 403
Express (Node.js)res.status(403).json({ error: "Forbidden" })
Flask (Python)abort(403) or return jsonify(...), 403
FastAPI (Python)raise HTTPException(status_code=403, detail="...")
Spring Security (Java)Throws AccessDeniedException → 403

Frequently Asked Questions

What is the difference between 401 and 403?

401 means you are not authenticated. 403 means you are authenticated but not authorized — you lack permission. Sending valid credentials fixes a 401; it does not fix a 403, which requires elevated permissions or access to the resource.

How do I fix a 403 Forbidden?

Check that the authenticated user has the right role and permissions, that the token carries the required scopes, and that the user actually has access to the specific resource. Also rule out IP or geo restrictions.

Why do I get 403 even though I am logged in?

Because authentication is not the problem — authorization is. Your account is valid but lacks permission for this specific action or resource. You need elevated permissions, the correct token scope, or access to that resource.

Should an API return 403 or 404 for a resource I can't access?

It depends on whether the resource's existence is sensitive. 403 confirms the resource exists but denies access; 404 hides whether it exists at all. For private/sensitive resources, returning 404 (as GitHub does for private repos) prevents unauthorized users from probing what exists.

Should I retry or refresh my token on a 403?

No. A 403 means your valid credentials lack permission — refreshing the token won't grant new permissions, and retrying will fail identically. Handle it as a permanent denial: show a message, or request elevated access. Token refresh is for 401, not 403.

Working with a JSON API response?

Format and inspect any response in your browser — nothing is uploaded.

JSON Formatter JSON Validator All HTTP Status Codes
About the author

Pasindu Ishan is a software developer based in Sri Lanka. He builds privacy-first developer tools at JSON Dev Tools.