Використання Content-Security-Policy разом із React & Emotion
Content-Security-Policy
(CSP) – це HTTP заголовок, який покращує безпеку веб-застосунків за рахунок заборони небезпечних дій, таких як завантаження та відправка даних на довільні домени, використання eval
, inline-скриптів і т.д. У цій статті буде зроблено фокус на директиві style-src
та її використання разом із CSS-in-JS бібліотекою emotion.
Коротко про CSP та style-src
Content-Security-Policy
заголовок повинен бути виставлений у відповіді разом із завантажуваною веб-сторінкою (наприклад, index.html
). Це виглядає так:
Content-Security-Policy: style-src 'self'
style-src
– це директива, яка відповідає за те, які стилі можна завантажувати та застосовувати на сторінці. Можливі значення:
'none'
– усі стилі заборонені'self'
– Дозволені файли стилів, які завантажуються з того ж домену, що і основний документ (сторінка)<url>
, наприкладhttps://example.com
– дозволені файли стилів з цього домену, також допускаються wildcard (*) на місці під-домену та порту'<hash-algorithm>-<base64-value>'
, наприклад'sha256-ozBpjL6dxO8fsS4u6fwG1dFDACYvpNxYeBA6tzR+FY8='
– дозволені файли стилів та inline -стилі (тег<style>
), у яких хеш збігається із зазначеним значенням'nonce-<value>'
, наприклад'nonce-abc'
– дозволяються inline -стилі, у яких атрибутnonce
збігається із зазначеним (у прикладі –abc
)'unsafe-hashes'
– дозволяє inline -стилі, зазначені в атрибутіstyle
рядком, наприклад<div style="color:red;"></div>
, при цьому хеш значення атрибута повинен збігатися з хешом, вказаним у'<hash-algorithm>-<base64-value>'
'unsafe-inline'
– дозволяє всі inline -стилі, створені через тег<style>
'unsafe-eval'
– дозволяє додати/змінити CSS declarations, які призводять до парсингу рядка, наприклад, за допомогою CSSStyleDeclaration.cssText
Директива може приймати кілька значень через пропуск. У цьому випадку це сприймається як логічне “або” – при задоволенні хоча б одного значення стилі дозволяються.
CSP та emotion
emotion
додає style
елементи динамічно і в останніх версіях не може виймати всі стилі в окремий файл під час складання програми. Це означає, що для того, щоб можна було використовувати emotion
разом з style-src
, є такі опції:
'unsafe-inline'
– Найпростіша опція з усіх. Не вимагає будь-яких налаштувань з бокуemotion
. При цьому ми знижуємо безпеку нашої програми, тому це рішення можна використовувати лише як тимчасове.'nonce-<value>'
– можна дозволити inline-стилі, створеніemotion
. Для цього потрібно задатиnonce
при створенніcache
.
При використанні @emotion/react
або @emotion/styled
це можна зробити так:
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}>
{/* children */}
</CacheProvider>
);
}
Якщо використовується @emotion/css
безпосередньо, то потрібно створити свій екземпляр emotion
:
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(),
});
При використанні createEmotion
потрібно змінити всі місця, де раніше імпортувався @emotion/css
на цей модуль:
// import { css } from "@emotion/css";
import { css } from "./emotion";
Передача nonce на фронтенд
Т.к. значення CSP заголовка недоступне коду, що виконується на клієнті, значення потрібно додатково передати іншим чином. Один із варіантів – це створення inline-скрипту зі значенням, яке виставляється на бекенді:
<script id="nonce" type="application/json">
"abc"
</script>
На фронтенді це можна використовувати таким чином:
function getNonceValue() {
const nonceElement = document.getElementById("nonce");
return JSON.parse(nonceElement.textContent);
}
Зверніть увагу на type="application/json"
– таким чином браузер не вважає це виконуваним кодом, і особливе значення script-src
не потрібно.