HTTP

Debug Guide

Debugging 403 Forbidden

403 means the server knows who you are but refuses access. Unlike 401, logging in won't help — you need the right permissions.

Step 1 — Confirm It's Actually 403

Open DevTools → Network → find the failing request → check the status code. A 403 response body often contains a hint:

{"error": "forbidden", "message": "Admin access required"}

Step 2 — Is It Authentication or Authorization?

SymptomLikely causeFix
No token sentMissing auth headerAdd Authorization: Bearer <token>
Token expiredJWT/session expiredRefresh token or re-login
Wrong token scopeToken lacks required permissionRequest token with correct scopes
Correct token, still 403Authorization failureUser lacks the required role/permission

Quick test: Try the same request with an admin token. If it works, it's an authorization (role/permission) issue, not authentication.

Step 3 — Common Causes

Missing or insufficient role

The most common cause. The user is authenticated but their role doesn't include the required permission.

// Express.js — check role before proceeding
app.delete('/api/posts/:id', requireAuth, (req, res) => {
  if (req.user.role !== 'admin' && req.user.id !== post.authorId) {
    return res.status(403).json({ error: 'Forbidden' })
  }
  // proceed with deletion
})

    

CSRF token missing or invalid

Form submissions and state-changing requests often require a CSRF token. If it's missing or expired, the server returns 403. Ensure your form includes the CSRF token and that it matches the server-side session.

IP allowlist / rate limiting

Some APIs restrict access by IP address. If your IP is not in the allowlist, you get 403. Check with the API provider.

File system permissions (server-side)

On Linux servers, if the web server process (nginx, Apache) doesn't have read permission on a file, it returns 403.

# Check file permissions
ls -la /var/www/html/file.txt

# Fix: give web server read access
chmod 644 /var/www/html/file.txt
chown www-data:www-data /var/www/html/file.txt

    

API key missing required scopes

OAuth tokens and API keys often have scopes. A token with read:users scope cannot perform delete:users.

function requireScope(scope) {
  return (req, res, next) => {
    if (!req.token.scopes.includes(scope)) {
      return res.status(403).json({
        error: 'insufficient_scope',
        required: scope
      })
    }
    next()
  }
}

    

Step 4 — Checklist

  • Is the user authenticated? (If not, it should be 401, not 403)
  • Does the user have the required role or permission?
  • Is a CSRF token required and present?
  • Is the request coming from an allowed IP?
  • Does the API token have the required scopes?
  • Are file/directory permissions correct on the server?
  • Is the resource behind a feature flag that's disabled for this user?

Frequently Asked Questions

What is the difference between 401 and 403?

401 Unauthorized means the server does not know who you are — authentication is missing or failed. 403 Forbidden means the server knows who you are but refuses access — you lack the required permission. Logging in fixes 401; it does not fix 403.

Should I return 403 or 404 for unauthorized access?

It depends on whether you want to reveal the resource exists. Return 403 to confirm the resource exists but deny access. Return 404 to hide the resource entirely (security through obscurity). For admin endpoints, 404 is often safer.

Why am I getting 403 on a file upload or form submission?

Most likely a CSRF token issue. Browsers send CSRF tokens with form submissions. If the token is missing, expired, or invalid, the server returns 403. Check that your form includes the CSRF token and that it matches the server-side session.