CSP vs CORS: why your fetch is blocked
A fetch() fails in the console with something about a policy, and it isn't obvious whether the browser is enforcing CORS or Content-Security-Policy. They're separate checks with separate fixes, and if you've just added a CORS proxy, the CSP one has a trap: you have to allowlist the proxy, not the API.
The proxy's origin must be in connect-src
Say you route a call through corsproxy.dev because the upstream API sends no Access-Control-Allow-Origin header, and your page also ships a locked-down CSP. The request now fails with a CSP violation instead of a CORS error, and the instinct is to add the upstream API to connect-src. That doesn't help.
connect-src governs which hosts the page's own fetch(), XMLHttpRequest, WebSocket, and EventSource calls may connect to. With a proxy, your page only ever connects to api.corsproxy.dev. The second hop, from the proxy to the upstream, happens server-side, where your page's CSP doesn't apply. So the proxy's origin is the one to allowlist:
// Page CSP needs: connect-src 'self' https://api.corsproxy.dev
// api.example.com does NOT need to be in connect-src: the browser
// never talks to it directly.
const target = encodeURIComponent('https://api.example.com/users');
fetch(`https://api.corsproxy.dev/proxy?url=${target}&key=YOUR_CORSPROXY_KEY`)
.then(r => r.json())
.then(data => console.log(data));
Two consequences worth knowing:
- One entry covers every upstream. Add
https://api.corsproxy.devonce; you don't need aconnect-srcentry per API you call through it. - Listing the upstream does nothing. Adding
api.example.comtoconnect-srcin that setup has no effect on the proxied call. Leave it out unless the page also calls that host directly.
Because one entry now opens a path to any host the proxy will fetch, it's worth limiting what your key can reach on the proxy side too, for example with allowed_targets (see the API docs).
Telling the two errors apart
The console text gives it away. A CORS error mentions the response and Access-Control-Allow-Origin:
Access to fetch at 'https://api.example.com/users' from origin
'https://app.mysite.com' has been blocked by CORS policy: No
'Access-Control-Allow-Origin' header is present on the requested resource.
A CSP violation says "Content Security Policy directive" and names the directive (usually connect-src, or the default-src it fell back to):
Refused to connect to 'https://api.example.com/users' because it
violates the following Content Security Policy directive:
"connect-src 'self'".
If you see "Content Security Policy directive" anywhere in the message, changing CORS settings on the target server won't help, because the request never reached it.
| CORS | CSP (connect-src) |
|
|---|---|---|
| Who sets the rule | The target server | Your own page |
| Where it's checked | On the response | Before the request is sent |
| Fixed by | Server config, or a proxy | Your own CSP header |
| Console mentions | Access-Control-Allow-Origin | "Content Security Policy directive" |
CSP is set by your own page, via a Content-Security-Policy response header or a <meta http-equiv="Content-Security-Policy"> tag. CORS is decided by headers the server you're calling sends back; the mechanics are in the CORS glossary entry and Why am I getting a CORS error?. Good CORS headers don't get you past connect-src, and a permissive CSP doesn't get you past CORS. A request has to clear both.
Fixing a CSP block
Add the host you're calling to connect-src in whatever sets your CSP header. In Express, for example:
app.use((req, res, next) => {
res.setHeader(
'Content-Security-Policy',
"default-src 'self'; connect-src 'self' https://api.corsproxy.dev https://api.example.com"
);
next();
});
A <meta http-equiv="Content-Security-Policy"> tag works too, but a real response header is better: the meta tag can't set every directive (notably frame-ancestors and report-uri), and it only applies from the point it appears in the document.
Don't fix this by deleting connect-src or widening it to *. The directive limits where data can be sent if something on the page is later compromised, so list the specific hosts you actually call.
Try it
Create a free key, then add api.corsproxy.dev to your connect-src.