A tight Access-Control-Allow-Origin list only controls which browsers may read a cross-origin response. It is not an authorization decision for who may act on which resource.
Browsers enforce CORS. Scripts, mobile apps, curl, and server-to-server callers do not. If your API returns 200 for DELETE /invoices/inv_4821 whenever a valid session cookie or bearer token is present, a CORS allowlist never blocked that delete.
What usually goes wrong:
- Teams treat “not allowed by CORS” as “not allowed for this user.”
- Object checks are skipped because “only our SPA can call this API.”
- List endpoints return every tenant’s rows; the SPA hides what CORS would have blocked anyway.
Patterns that hold up:
- Authenticate the caller, then authorize each action on each resource id (ownership, role on that object, or an explicit grant).
- Filter list and search results by what the subject may see, on the server.
- Re-check on nested ids in batch and GraphQL paths.
Quick check: call the same mutating endpoint with a valid token from curl (no browser). If it succeeds for another user’s resource, CORS was never your authorization layer.
CORS is a browser reading rule. Object authorization belongs on the server.
Top comments (0)