CORS on 401, 404 and 500 Responses: Make Errors Readable
By Ankit Jain · Updated
Apply origin permission to intended readable error responses and distinguish authentication failure from CORS failure.
An API error response can have valid CORS permission. JavaScript should be able to read its HTTP status so the frontend can display an error or request authentication.
Why only errors fail
A normal route may add CORS headers, while an authentication filter, error handler, proxy, or CDN generates a response without them. The browser then hides that response and the frontend sees a fetch failure instead of the real status.
For an approved origin, this is a readable authentication error:
HTTP/1.1 401 Unauthorized
Access-Control-Allow-Origin: https://app.example.com
Vary: Origin
Content-Type: application/json
{"error":"authentication_required"}
Add Allow-Credentials: true if the request uses credentials: include. Do not expose unnecessary error details or grant unapproved origins access.
fetch uses its default credentials setting. The request has no cross-origin session cookie or Authorization header. This server requires a session and returns 401.
Page: https://app.example.com
Server API: https://api.example.com
Loading diagram. You can read the request and response details below while it loads.
On a small screen, scroll sideways to see the full diagram.
Read request and response details
-
JavaScript → Browser: Request a protected resource
const response = await fetch('https://api.example.com/profile'); -
Browser → Server API: Actual request without a session
GET /profile Origin: https://app.example.com -
Server API → Browser: Authentication error with CORS permission
401 Unauthorized Access-Control-Allow-Origin: https://app.example.com Vary: Origin Content-Type: application/json {"error":"authentication_required"} -
Browser check: Response access allowed
The origin is permitted. An HTTP 401 does not fail this response’s CORS check.
-
Browser → JavaScript: fetch resolves; response.ok is false
response.type // "cors" response.status // 401 response.ok // false await response.json() // {"error":"authentication_required"}Your code can inspect status 401 and show the sign-in flow. HTTP 401 alone does not enter fetch’s rejection handler.
With CORS permission, JavaScript can read the 401 and its error body. Without permission, fetch rejects with a TypeError and hides the response’s status and body. Network failures can also cause a TypeError.
Only the relevant headers and bodies are shown. Browsers add other headers. Your code reads the response body with response.json() or another body-reading method.
Handle the result in fetch
const response = await fetch('https://api.example.com/profile');
if (response.status === 401) {
// Show your application's sign-in flow.
} else if (!response.ok) {
throw new Error(`API returned ${response.status}`);
}
fetch normally resolves for HTTP errors when the response is readable. Permission or network failures can reject before your code can inspect a status.
Verify each response owner
Run middleware early enough for application failures and configure gateway-generated failures at the gateway. Test a controlled unauthorized request, a missing route, and a controlled server error in a non-production environment. Never create a production failure just to test headers.
See framework configuration and missing origin permission.
Practice readable API errors
Use Beeceptor mock rules to return controlled status codes and response headers while developing a frontend error flow. Use synthetic data, then repeat the check against your real API. A mock verifies your frontend behavior against that mock’s policy; it does not change your production server’s CORS configuration. Read the Beeceptor mock-rule guide.