DEV Community

Alexander Polyankin
Alexander Polyankin

Posted on

Using Content Security Policy headers with React & emotion

Content Security Policy (CSP) headers add another layer of security by disallowing unsafe actions, such as establishing connections with arbitrary domains, usage of eval, inline scripts, and others. This article will focus on the style-src directive and its usage with emotion.

Using CSP headers

Content-Security-Policy header should be set in the response to the browser when the application page is requested (e.g. index.html). It looks like this:

Content-Security-Policy: style-src self;
Enter fullscreen mode Exit fullscreen mode

style-src is a directive specifying which styles are allowed on the page. Possible values include:

  • self - styles served from the same domain
  • URL, e.g. https://example.test
  • unsafe-eval - dynamically creating stylesheets from strings, e.g. by CSSStyleSheet.insertRule()
  • unsafe-inline - inline <style></style> tags
  • nonce-<value>, e.g. nonce-abc - allows creating inline style tags only with the specified nonce attribute value. The value should be coming from a cryptographically secure random token generator and re-created on every request.

Modifying element styles via DOM APIs that do not involve CSS parsing is allowed unless JS execution is blocked by the script-src directive. The following would work:

element.style.display = "none";
Enter fullscreen mode Exit fullscreen mode

The following will not work unless unsafe-eval is enabled:

element.setAttribute("style", "display: none;");
Enter fullscreen mode Exit fullscreen mode

CSP headers and emotion

emotion inserts inline style tags and cannot be currently configured to extract styles to a static file at build-time. It means that if we need to enable CSP headers with emotion the following options are available:

  1. unsafe-inline. This option doesn't require any frontend code changes but it will not bring any security benefits.
  2. nonce-<value>. This option can be used to only allow inline style tags created by emotion in the app. It is the most secure approach with emotion. To make it work, emotion needs to be aware of the nonce value set in the page response headers. The exact configuration depends on how emotion is used in your app.

If you are using @emotion/react or @emotion/styled you would need to provide a custom cache with nonce set:

import { CacheProvider } from '@emotion/react'
import createCache from '@emotion/cache'

export function App() {
  const cache = createCache({
    key: 'my-app',
    nonce: getNonceValue(),
  });

  return (
    <CacheProvider cache={cache}>
      ...
    </CacheProvider>
  );
}
Enter fullscreen mode Exit fullscreen mode

If you are using @emotion/css you would need to create a custom emotion instance:

import createEmotion from '@emotion/css/create-instance'

export const {
  flush,
  hydrate,
  cx,
  merge,
  getRegisteredStyles,
  injectGlobal,
  keyframes,
  css,
  sheet,
  cache
} = createEmotion({
  key: 'my-app',
  nonce: getNonceValue(),
});
Enter fullscreen mode Exit fullscreen mode

Please note that with this approach you would need to change all places where you previously used @emotion/css directly to import the module where createEmotion is called:

// import { css } from "@emotion/css"; 
import { css } from "./emotion.ts";
Enter fullscreen mode Exit fullscreen mode

Passing nonce value to the frontend

CSP headers cannot be inspected by the frontend, so the value should be also set elsewhere. One of the common approaches is to set the value in an inline script tag by the backend:

<script id="nonce" type="application/json">
  "abc"
</script>
Enter fullscreen mode Exit fullscreen mode

The frontend could use it later like this:

function getNonceValue() {
  const nonceElement = document.getElementById("nonce");
  return JSON.parse(nonceElement.textContent);
}
Enter fullscreen mode Exit fullscreen mode

Please note that there is a custom type attribute on the script tag. This is required to make the browser ignore the script body and not to execute it, which won't work with certain script-src directive values.

Top comments (4)

Collapse
 
uladzimirdev profile image
Uladzimir Havenchyk

Nice, it will be super helpful for my project

Collapse
 
lisenish profile image
Dmitry Ivanov

Should I generate nonce value on the backend?

Collapse
 
ranquild profile image
Alexander Polyankin

Yes, definitely, using a cryptographically secure random generator.

Collapse
 
ning_de_a4ed52031fd9fc618 profile image
Ning De

can only work for server side rending, right?