Lately I’ve been digging into JSS delivery topologies and in particular SSR proxy options. As a starting point, I’ve been using the sample node headless proxy from the Sitecore JSS Github repo. In my dev scenario I was hitting a local Standalone Sitecore instance in Docker, and immediately noticed the images being referenced from Sitecore were getting blocked by the Content Security Policy (CSP). This is due to the CSP being set on all requests to the CM via a web.config setting, and then it being repeated back to the browser via the node proxy.

This usually wouldn’t be an issue outside a local dev environment as a pure CD (rather than Standalone/CM) would be used as the API host…which doesn’t set a CSP by default. OOTB CM/Standalone instances set a CSP since 9.3 (Jeremy Davis write about it here). All of this did get me thinking though….If the CD isn’t setting a CSP, how and where SHOULD it be set? How would you go about doing it in a headless architecture?
What is a CSP?
CSPs are a pretty easy way for developers to help protect their sites against script injection and XSS attacks. It’s a standard HTTP header that allows developers to supplement rules to the same origin policy in browsers. It can define allowed origins of scripts, images and other resources a webpage can access. All modern sites really should be leveraging a CSP. Read up on them here. There are some great tools for monitoring, generating and validating CSPs from Report URI. Also worth checking out Bas Litjen’s post about his module to help manage CSPs in Sitecore
Where should a CSP be set?
As always…..that depends on your requirements. In a headless architecture there are two options to investigate:
Dynamically from the CD
Sometimes CSPs need to change regularly, or individual pages/sites need to be targeted with a specific CSP. Allowing these settings to reside and thus be edited in Sitecore makes sense for these cases.
- Allows for Multisite scenarios
- Allows Sitecore users to edit the CSP in Content Editor
- Requires some dev investment to custom build or implement/configure a module
- Don’t just hardcode it in the web.config if you expect a multisite scenario!
On the Node Proxy
- Allows for Multisite scenarios
- Requires code/environment changes on the Node proxy
- Minimises traffic between the Proxy and CD (Some CSPs can get bulky!)
- No “creating” CSP on the fly, so…fast.
- Can be injected easily via Environment variables
Fixing the issue locally
Well the quick and dirty way to get this running locally is to simply remove the CSP. While fine for local dev and testing, I’d highly recommend reading on below to address the issue. Interestingly recent changes to the “release 15.0.0” (Update: v15 has now been officially released and includes removing the CSP in the sample) branch of the JSS node proxy sample does exactly this by leveraging the setHeaders function in the proxyOptions config, allowing you to manipulate headers in the proxy response.
https://github.com/Sitecore/jss/blob/5b50e70905dbecc75150f9a47bc3b87757e6024a/samples/node-headless-ssr-proxy/config.js#L101-L107
EG. Delete the header from the proxied response.
setHeaders: (req, serverRes, proxyRes) => {
delete proxyRes.headers['content-security-policy'];
},
Add a custom CSP on the proxy
While the above will happily remove all CSPs from the response, it means we no longer have a CSP at all. But we can use the same method to inject our own CSP. In the case of the node sample app, it would only ever be used for a specific Sitecore site (there would be multiple node instance clusters in a multisite scenario), so we can inject a new CSP at runtime.
setHeaders: (req, serverRes, proxyRes) => {
//set custom CSP here. Could use Env vars if required.
proxyRes.headers['content-security-policy'] = `default-src 'self' cdn.example.com myApiHost.example.com;`
},
Depending on your requirements, the CSP could be generated from parameters or environment variables at deploy or run time.