In order to protect against "the baddies of the internet" in this day and age, you are going to want to ensure you have the best security you can in and around your overall Solution. The best security involves a number of layers of security at both an infrastructure layer utilising tools such Firewalls/WAFS and point-to-point/VPN connections between your infrastructure as well as at the application layer. Given the current movement away from the 'monolith' solution towards headless architecture and SaaS, this is more and more important as we now required to protect more than just a couple of things and  integrate with a couple of external services - our headless solution is more likely to deploy a number of client facing applications and when we're looking at SaaS solutions, its not always possible to just turn lock down our own infrastructure and white list a few IPs as most SaaS offerings don't offer a private constant connection and IPs to whitelist unless you are willing to pay big money. This leaves can leave us a little exposed and in need of ensuring that we have done everything we can at the application layer as our last line of defence to protect ourselves.

This article covers what you need to know to apply the necessary Security Headers to lock down your Headless Sitecore solution at the application layer. We're using a NextJS Headless solution deployed to Vercel and Sitecore CMS deployed to Azure PaaS.

Before you start, it's generally easiest to ensure you have a decent infrastructure architecture/topology diagram which lists what is communicating with what as well as any 3rd party integration points you have.

Security Headers for JSS and NextJS

Sitecore Security Headers

We're going to use the web.config file to manage all of the Sitecore Application level Security Headers and apply them to the Content Management (CM) and Content Delivery (CD) Roles. The same principles and approach applies to both however you can use your own best judgement as to what should be applied on each. If you consider what is happening in a JSS Solution using Server Side Rendering, both CM and CD are sending and recieving data from the Rendering Host (in our case it's our NextJS application over in Vercel) so allowances will need to be made for that here.

By default, the /Sitecore path will have security headers in place in the web.config which look like this:

<?xml version="1.0" encoding="utf-8"?>
  <location path="sitecore">
          <remove name="X-Content-Type-Options"/>
          <remove name="X-XSS-Protection"/>
          <remove name="Content-Security-Policy"/>
          <add name="X-XSS-Protection" value="1; mode=block"/>
          <add name="X-Content-Type-Options" value="nosniff "/>
          <add name="Content-Security-Policy"
            value="default-src 'self' 'unsafe-inline' 'unsafe-eval'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; font-src 'self' 'unsafe-inline'; upgrade-insecure-requests; block-all-mixed-content;"/>

This only covers the /sitecore path however and we want to apply this to all other paths. The "Content Security Policy" (CSP) headers information can be found here but the general gist is that anything that you want to ALLOW to accept requests for shouls be added to this list into the relevant request 'type' list. Eg. if you want to allow IFrames from somewhere, use the appropriate section, same for scripts and fonts etc. An example is shown below.

Note: If you haven't configured custom domains for your vercel application or you want the default vercel-generated domains to work you will need to include them in this list. Either way, you need to add allowances for your Head(s) to be allowed to access resources and visa versa. Note the Tokenised #{Client.Header.Domains} tokens we are using to swap in environment-specific values as part of CI/CD for the Sitecore Side. Obviously think about the environment type and how much you want to expose via these headers. The * allow rule might not be the best idea for a non-locked down produciton environment for example as it means anyone making requests from an azure defualt domain will be allowed.

<?xml version="1.0" encoding="utf-8"?>
<configuration xmlns:xdt="">
<httpProtocol xdt:Transform="Replace">
        <remove name="X-Powered-By" />        
        <remove name="X-XSS-Protection" />
        <remove name="X-Content-Type-Options" />
        <remove name="Content-Security-Policy" />
        <remove name="Strict-Transport-Security" />
        <add name="X-XSS-Protection" value="1; mode=block" />
        <add name="X-Content-Type-Options" value="nosniff " />
        <add name="Content-Security-Policy" value="default-src 'self' 'unsafe-inline' 'unsafe-eval' #{Client.Header.Domains} https://* https://* https://* https://*; script-src 'self' 'unsafe-inline' 'unsafe-eval' #{Client.Header.Domains} https://* https://* https://* https://* https://*; img-src 'self' #{Client.Header.Domains} https://* https://* https://* data:; style-src 'self' 'unsafe-inline' #{Client.Header.Domains} https://* https://* https://* https://*; font-src 'self' 'unsafe-inline' #{Client.Header.Domains}  https://* https://* https://*; upgrade-insecure-requests; block-all-mixed-content;" />
        <add name="Strict-Transport-Security" value="max-age=63072000; includeSubDomains; preload" />

Vercel / Sitecore JSS Security Headers

For our Vercel Heads / Front End Applications, you will need to apply the headers in 2 (two) places.

The first is within the next.config.js file. For reusabilities sake, we have added them into their own config file and imported it like so:

The second location which you need to include these headers is within any of your "getServerSideProps" methods. The most notable of these being the Sitecore Headless 'page' located under /src/pages/[[...path]].tsx .

I've put these together in the same file for ease of capturing but anywhere where we are calling out for something simlar to this should be reviewed to ensure you're including the correct security headers when appropirate.

Its worth noting that having these enabled on your local development machine can be a pain. You may want to provide an env variable to toggle it on and off in your local development environment and just enable it when testing locally and obviously in your higher level environments and production.