Add CSP to XMCloud
Tags: sitecore, xmcloud
Published: 2024-03-11 21:14:37

It is common that you are going to need to pull in external content and use or display it with your website. So, we need to tell the browser where those assets can come from by setting a few values in the header.

## The Plugin

This new file will be based on the [cors-header.js](https://github.com/sitecorelabs/xmcloud-foundation-head/blob/main/src/sxastarter/src/lib/next-config/plugins/cors-header.js) that is part of the base packages for XMCloud. So we need to create a new plug-in file. I named mine 'csp-header.js'.

`src\<project name>\src\lib\next-config\plugins\csp-header.js`

```
const config = require('../../../temp/config');

/**
 * @param {import('next').NextConfig} nextConfig
 */
const cspHeader = (nextConfig = {}) => {
  if (!config.sitecoreApiHost) {
    return nextConfig;
  }
  return Object.assign({}, nextConfig, {
    async headers() {
      const extendHeaders = typeof nextConfig.headers === 'function' ? await nextConfig.headers() : [];

      var fullCSP = '';

      fullCSP = fullCSP.concat(concatAttributes('default-src', config.customCSPDefault));
      fullCSP = fullCSP.concat(concatAttributes('img-src', config.customCSPImg));


      if(fullCSP.length > 0) {
        return [
          ...(await extendHeaders),
          {
            source: '/:path*',
            headers: [
              {
                key: 'Content-Security-Policy',
                value: fullCSP,
              },
            ],
          }
        ];
      }
      else { return [ ...(await extendHeaders) ];}
    },
  });
};

function concatAttributes(cspType, cspValue) {
  var returnAttribute = '';
  if(cspValue !== 'undefined' && cspValue.length > 0) {
    returnAttribute = cspType.concat(' ', cspValue.replace(/\\/g, ''), ';');
  }
  return returnAttribute;
}

module.exports = cspHeader;

```

There are a couple of things to point out here. I chose to pull this information in from a config so it could be changed. You could easily just put your CSP values in place of these lines

```
fullCSP = fullCSP.concat(concatAttributes('default-src', config.customCSPDefault));
fullCSP = fullCSP.concat(concatAttributes('img-src', config.customCSPImg));
```

It is also worth noting that you can also customize which pages this applies by adjusting the source.  

`source: '/:path*',`

The last thing that you will need to know is you will <u>need top stop and start your instance</u> before this plugin is picked up by the application. Expect a decent amount of `up.ps1` and `down.ps1` while you are doing your testing.

## Adding Config Entries

Working through this on a docker instance. I wanted to be able to place a value directly in the `.env` similar to how it would get set when you are setting up a hosting environment.

```
CUSTOM_CSP_DEFAULT="\'self\' \'unsafe-inline\' \'unsafe-eval\' data:"
CUSTOM_CSP_IMG="\'self\' data:"
```

I added these to my `.env`, but they need to wired into the config to be used by the plugin. To do this you will need to edit the file:

`src\<project name>\scripts\generate-config.ts` 

I removed some lines and replaced them with `...` below, but you will want to add an entry for each config line you want incorporated. Note that you can use the parameter directly, or use the `constantCase` function to pull it in. It will make the  translation between the value `CUSTOM_CSP_DEFAULT` to  `customCspDefault` when it is called.

```
const defaultConfig: JssConfig = {
...
  customCSPDefault: process.env[`${constantCase(`customCspDefault`)}`],
  customCSPImg: process.env[`${constantCase(`customCspImg`)}`],
...
};
```


## Thanks

A lot of this came from help I received in the Sitecore Slack community. If you are not already there, I suggest you sign up.

Also the people on my team who put up with me on a daily basis.