Использование заголовков политики безопасности содержимого в React & emotion
Заголовки Content Security Policy (CSP) добавляют еще один уровень безопасности, запрещая небезопасные действия, такие как установление соединений с произвольными доменами, использование eval
, inline-скриптов и др. В данной статье речь пойдет о директиве style-src
и ее использовании с emotion
.
Использование CSP-заголовков
Заголовок Content-Security-Policy
должен быть установлен в ответе браузеру при запросе страницы приложения (например, index.html
). Выглядит он следующим образом:
Content-Security-Policy: style-src self;
style-src
- это директива, определяющая, какие стили разрешены на странице. Возможные значения включают:
self
- стили, обслуживаемые с одного домена- URL, например,
https://example.test
unsafe-eval
- динамическое создание таблиц стилей из строк, например, с помощьюCSSStyleSheet.insertRule()
unsafe-inline
- инлайн-теги<style></style>
nonce-<value>
, например,nonce-abc
- позволяет создавать теги инлайн-стилей только с указанным значением атрибутаnonce
. Значение должно быть получено от криптографически защищенного генератора случайных токенов и пересоздаваться при каждом запросе.
Изменение стилей элементов с помощью DOM API, не включающих разбор CSS, разрешено, если выполнение JS не заблокировано директивой script-src
. Для этого подойдет следующее:
element.style.display = "none";
Следующие действия не будут работать, если не включена функция unsafe-eval
:
element.setAttribute("style", "display: none;");
CSP-заголовки и emotion
emotion
вставляет инлайн-теги style
и в настоящее время не может быть настроен на извлечение стилей в статический файл во время сборки. Это означает, что если нам необходимо включить CSP-заголовки с помощью emotion
, то доступны следующие варианты:
unsafe-inline
. Этот вариант не требует изменения кода фронтенда, но не дает никаких преимуществ в плане безопасности.nonce-<value>
. Эта опция может быть использована для того, чтобы разрешить использование в приложении только инлайн-тегов стиля, созданныхemotion
. Это наиболее безопасный подход к работе сemotion
. Чтобы он работал,emotion
должен знать о значенииnonce
, установленном в заголовках ответа страницы. Точная конфигурация зависит от того, как используетсяemotion
в вашем приложении.
Если вы используете @emotion/react
или @emotion/styled
, то вам необходимо предоставить собственный кэш с установленным nonce
:
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>
);
}
Если вы используете @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(),
});
Обратите внимание, что при таком подходе необходимо изменить все места, где ранее использовался @emotion/css
, на импорт модуля, в котором вызывается createEmotion
:
// import { css } from "@emotion/css";
import { css } from "./emotion.ts";
Передача значения nonce в фронтенд
CSP-заголовки не могут быть проверены фронтендом, поэтому значение должно быть задано в другом месте. Одним из распространенных подходов является установка значения script
в инлайн-теге бэкенда:
<script id="nonce" type="application/json">
"abc"
</script>
В дальнейшем фронтенд может использовать его следующим образом:
function getNonceValue() {
const nonceElement = document.getElementById("nonce");
return JSON.parse(nonceElement.textContent);
}
Обратите внимание на наличие атрибута пользовательского типа у тега script
. Он необходим для того, чтобы браузер игнорировал тело скрипта и не выполнял его, что не работает при определенных значениях директивы script-src
.
Спасибо за прочтение! Счастливого кодинга!