Використання 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не потрібно.