An HTTP 403 Forbidden response means the server understood your request but refuses to authorize it. Unlike a 404 (not found) or a 500 (server error), a 403 is about permissions — the resource exists, and the server knows you're asking for it, it just won't give it to you. This guide covers every common cause and exactly how to fix each one.
401 vs 403 — Key Difference
401 Unauthorized
Not authenticated — the server doesn't know who you are. No credentials were provided, or they were invalid/expired.
Fix: Provide valid credentials (log in again, refresh your token).
403 Forbidden
Not authorized — the server knows who you are but your account lacks the required permission role or the resource is explicitly blocked.
Fix: Check permission roles, resource policies, IP allowlists, or API key scopes.
Note: Some APIs return 403 for both authentication and authorization failures for security reasons (to avoid leaking whether a resource exists).
8 Causes of 403 Errors (and How to Fix Each)
1.Missing or Expired Bearer Token
The most common cause. Your request is missing the Authorization header, or the JWT token has expired.
Broken — missing header:
const res = await fetch('https://api.example.com/profile');
// No Authorization header → 403 ForbiddenFixed:
const token = localStorage.getItem('access_token');
const res = await fetch('https://api.example.com/profile', {
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
},
});
if (res.status === 401) {
// Token expired — redirect to login or refresh
await refreshToken();
}2.Wrong API Key Format
Different APIs expect the API key in different ways — as a header, query parameter, or with a specific prefix.
// As X-API-Key header (most common)
fetch(url, { headers: { 'X-API-Key': 'your-key-here' } });
// As Authorization: ApiKey prefix
fetch(url, { headers: { 'Authorization': 'ApiKey your-key-here' } });
// As query parameter (less secure, avoid for sensitive APIs)
fetch(`${url}?api_key=your-key-here`);
// OpenAI-style
fetch(url, { headers: { 'Authorization': 'Bearer sk-...' } });Check the API's documentation for the exact header name and value format expected.
3.CORS + 403 Combined (Preflight Blocked)
Some servers (especially with WAF or auth middleware) apply authentication to the preflight OPTIONS request too. The browser sends an unauthenticated OPTIONS preflight and gets back a 403.
Symptom:
Network tab shows OPTIONS request returning 403 before the actual POST is ever sent.
Fix (Express) — allow OPTIONS without auth:
// Respond to OPTIONS before auth middleware runs
app.options('*', cors());
// Then apply auth middleware only to other methods
app.use((req, res, next) => {
if (req.method === 'OPTIONS') return next();
return authMiddleware(req, res, next);
});4.Cloudflare WAF Blocking Bot Traffic
Cloudflare's WAF or Bot Management can return 403 when it detects automated traffic patterns. The HTML response body usually mentions "Cloudflare" and a Ray ID.
Fixes:
- Add a realistic
User-Agentheader to your request - Check the Cloudflare dashboard → Security → Events for the blocked rule ID
- Add a WAF rule exception for your IP or User-Agent
- If using Cloudflare Access, ensure your service token is included
# cURL with User-Agent (avoids some WAF blocks)
curl -H "User-Agent: Mozilla/5.0 (compatible; MyApp/1.0)" \
-H "Authorization: Bearer $TOKEN" \
https://api.example.com/data5.IP Allowlist / Blocklist
Some APIs restrict access to specific IP addresses. If your server's IP is not on the allowlist — or is on a blocklist — every request returns 403 regardless of token.
Fixes:
- Find your server's egress IP and add it to the API's allowlist
- Use a static IP or Elastic IP (AWS) for consistent outbound traffic
- Check if your IP is on a public blocklist (e.g., Spamhaus)
6.AWS API Gateway Resource Policy
AWS API Gateway returns 403 for missing API keys, resource policy denials, or IAM authorization failures. The error body is usually { "message": "Forbidden" } with no further details.
Checklist:
- If the stage uses API Key required, add
x-api-keyheader - If using IAM auth, sign the request with AWS Signature V4 (or use an SDK)
- Check the resource policy — ensure your caller's IP or VPC is allowed
- Enable CloudWatch logging in API Gateway to see the exact denial reason
# Test AWS API Gateway with API key
curl -H "x-api-key: your-api-key-here" \
https://abc123.execute-api.us-east-1.amazonaws.com/prod/resource7.Nginx auth_basic Blocking
Nginx with auth_basic enabled returns 401 first, but if credentials are wrong it returns 403. Also, deny directives in nginx.conf can block specific IPs or all traffic.
# Send Basic Auth credentials
curl -u username:password https://api.example.com/data
# In fetch
const credentials = btoa('username:password');
fetch(url, {
headers: { 'Authorization': `Basic ${credentials}` }
});8.Missing Required Header (e.g., X-API-Key)
Some APIs require a custom header beyond Authorization — like X-API-Key, X-Client-ID, or a tenant/workspace ID. Forgetting any required header returns 403.
fetch('https://api.example.com/data', {
headers: {
'Authorization': `Bearer ${token}`,
'X-API-Key': apiKey,
'X-Workspace-ID': workspaceId, // tenant-specific header
'Content-Type': 'application/json',
},
});How to Debug a 403 Error — Step by Step
- 1
Read the response body
Open DevTools → Network tab → click the request → Response tab. Well-designed APIs return JSON with an error code or message. Cloudflare returns HTML. AWS returns {"message":"Forbidden"}.
- 2
Check your token
Paste your JWT at jwt.io and check the exp field (Unix timestamp). If it's in the past, the token is expired. Re-authenticate and retry.
- 3
Replay with cURL
Use our HAR to cURL tool to generate an exact cURL replay of the browser request. Run it in a terminal. If cURL works but fetch doesn't, compare the headers closely.
- 4
Check server logs
If you control the server: check application logs and WAF logs for the exact reason. AWS CloudWatch, Cloudflare Security Events, and Nginx access.log are the places to look.
Inspect your actual request headers
Convert your browser's HAR file to a cURL command to replay and debug the exact request that's getting a 403.
Inspect your actual request headers with HAR to cURLFrequently Asked Questions
What is the difference between 401 and 403?
401 means you are not authenticated — no credentials or invalid credentials. 403 means you are authenticated but lack permission. Fix 401 by logging in; fix 403 by checking roles, scopes, and resource policies.
How do I fix 403 in fetch/axios?
Add the correct Authorization header: headers: { "Authorization": `Bearer ${token}` }. Also verify the token is not expired and your account has the required permissions for that specific endpoint.
Why does Cloudflare return 403?
Cloudflare's WAF or Bot Management blocked the request. Check the Security Events dashboard for the specific rule ID. Adding a real User-Agent header often helps; longer term, create a WAF exception for your traffic.
How do I fix 403 in AWS API Gateway?
Most common causes: missing x-api-key header, IAM authorization failure, or a resource policy blocking your IP. Enable CloudWatch execution logging on the API Gateway stage to get the exact denial reason in the logs.
Can CORS cause a 403 error?
Yes. If the server (or WAF in front of it) applies authentication to preflight OPTIONS requests, the unauthenticated OPTIONS will get a 403. The fix is to bypass authentication for OPTIONS requests on the server, and ensure your WAF allows OPTIONS through. See CORS error fix guide.