Использование заголовков политики безопасности содержимого в 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.
Спасибо за прочтение! Счастливого кодинга!