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;
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. byCSSStyleSheet.insertRule()
-
unsafe-inline
- inline<style></style>
tags -
nonce-<value>
, e.g.nonce-abc
- allows creating inline style tags only with the specifiednonce
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";
The following will not work unless unsafe-eval
is enabled:
element.setAttribute("style", "display: none;");
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:
-
unsafe-inline
. This option doesn't require any frontend code changes but it will not bring any security benefits. -
nonce-<value>
. This option can be used to only allow inline style tags created byemotion
in the app. It is the most secure approach withemotion
. To make it work,emotion
needs to be aware of thenonce
value set in the page response headers. The exact configuration depends on howemotion
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>
);
}
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(),
});
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";
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>
The frontend could use it later like this:
function getNonceValue() {
const nonceElement = document.getElementById("nonce");
return JSON.parse(nonceElement.textContent);
}
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)
Nice, it will be super helpful for my project
Should I generate
nonce
value on the backend?Yes, definitely, using a cryptographically secure random generator.
can only work for server side rending, right?