К содержанию
ActionPulse

CORS и разрешённые origin

Как приём событий ActionPulse проверяет origin браузерного запроса, что настроить в проекте, как ведёт себя preflight и какие директивы нужны в политике безопасности контента.

Кому
Фронтенд-разработчикам и администраторам сайта
Проверено
На этой странице

Когда события отправляет страница, а не бэкенд, в дело вступают две независимые проверки. Первая — обычный CORS браузера: он решает, позволено ли странице прочитать ответ с другого домена. Вторая — список разрешённых origin у браузерного токена: его проверяет сам приём и отказывает, если адрес страницы в списке не значится.

Различать их важно, потому что почти все «проблемы с CORS» в ActionPulse — это вторая проверка. Транспорт настроен так, чтобы браузер не мешал: запросы приёма отвечают разрешением для любого домена, а решение принимает сервер по типу ключа, области действия и origin.

Как приём проверяет origin

Проверка касается только браузерного токена. Приём берёт заголовок Origin запроса, приводит его к каноническому виду и сравнивает на точное совпадение со списком, заданным при выпуске токена.

Правила, зашитые в проверку:

  • допустимы только схемы http и https;
  • маски запрещены — адрес вида https://*.example.com не примут ни при выпуске, ни при сравнении;
  • в origin не может быть пути, параметров, фрагмента и имени пользователя;
  • пустой список origin означает отказ всегда — токен без списка нерабочий;
  • в списке от 1 до 32 адресов.

Что при сравнении не важно

Эти различия приводятся к одному виду и на результат не влияют:

Запись Приводится к
HTTPS://Example.COM регистр схемы и хоста снимается
https://example.com/ завершающий слеш убирается
https://example.com. завершающая точка в имени хоста убирается
https://example.com:443 порт по умолчанию для схемы убирается
http://example.com:80 порт по умолчанию для схемы убирается

Поддомен, схема и порт — разные origin

А эти различия важны, и каждое даёт отдельный origin:

Пара адресов Одно и то же?
https://example.com и https://www.example.com нет, поддомен считается отдельно
https://example.com и http://example.com нет, схема входит в origin
https://example.com и https://example.com:8443 нет, непустой порт входит в origin
https://shop.example.com и https://blog.example.com нет, перечисляйте оба

Поэтому список нужно составлять по фактическим адресам, а не по домену: production, вариант с www и без него, поддомены со своими страницами, адрес предпросмотра сборок, адрес стенда разработки со схемой и портом.

Что настроить в проекте

Список origin задаётся один раз — при выпуске браузерного токена.

  1. Откройте Настройки → Проект, секция «Credentials проекта». Тот же выпуск есть на экране Настройки → Установка трекера, секция «Доступ к сбору данных».
  2. В блоке «Browser token» заполните поле «Разрешённые origins» — адреса через запятую или с новой строки — и нажмите «Выпустить browser token».
  3. Скопируйте полное значение токена сразу: повторно оно не показывается.
  4. Подставьте токен в тег или в вызов инициализации.

Ошибки ввода приходят сразу: пустой список, маска в адресе, адрес с путём, параметрами, фрагментом или логином и паролем. Проверки в кабинете и на сервере одинаковые, авторитетен сервер.

Выпуск токена доступен роли администратора и владельцу проекта. Подробнее о типах ключей и о том, что ещё привязано к токену, — в типах ключей.

Preflight и заголовки

Трекер отправляет события с заголовками Authorization и X-PP-Tracker, поэтому браузер сначала делает предварительный запрос OPTIONS. Тип содержимого при этом намеренно простой (text/plain), чтобы не добавлять к проверке ещё одно условие.

Предварительный запрос обрабатывается до всякой авторизации и всегда получает 204. Он не зависит ни от токена, ни от списка origin, ни от тарифа. Практический вывод: если OPTIONS не получил ответа или получил ошибку, дело не в настройках ActionPulse — смотрите на свой прокси, WAF или расширение браузера.

Отдельный случай — отправка на выходе со страницы: там запрос уходит без заголовков, поэтому предварительная проверка ему не нужна и в сетевой панели её не будет. Публичный токен в этом случае едет в теле запроса.

Ответ на запросы приёма несёт:

Заголовок Значение
Access-Control-Allow-Origin *
Access-Control-Allow-Methods POST, OPTIONS
Access-Control-Allow-Headers Authorization, Content-Type, X-PP-Tracker, X-PP-Source, X-PP-SDK-Name, X-PP-SDK-Version, X-PP-App-Version, X-PP-Release
Access-Control-Max-Age 600 — результат проверки браузер кэширует 10 минут
Vary Origin

Заголовок Access-Control-Allow-Credentials не выставляется никогда: приём не использует cookie, и разрешение для любого домена не даёт сторонним сайтам отправлять запросы от имени вошедшего пользователя.

Файлы трекера — отдельная история: версионные файлы и манифест отдаются с разрешением для любого домена, разрешёнными GET, OPTIONS, заголовком If-None-Match и открытым ETag. Именно поэтому к версионному адресу можно применять crossorigin="anonymous" вместе с integrity. Для совместимого адреса /v1/t.js проверка целостности недоступна в принципе — там нечего фиксировать, содержимое не привязано к версии.

Если origin не совпал

Симптомов два, и оба тихие.

Запрос события получает 403:

{ "error": { "code": "credential_origin_forbidden",
             "message": "browser credential origin is not allowed", "details": {} } }

Это не ошибка CORS. Ответ приходит с разрешением для любого домена, поэтому страница его читает, а в консоли нет сообщения про заблокированный запрос. Увидеть отказ можно только во вкладке «Сеть»: запрос завершён, код 403. Трекер такой ответ не повторяет и считает партию доставленной — события просто исчезают без единой записи в консоли.

Публичная конфигурация сбора отвечает «всё выключено»: запрос GET /v1/cfg для несовпавшего origin получает 200 и заведомо безопасный ответ — автосбор выключен, записи сессий выключены, доля записываемых сессий нулевая, диагностика выключена. Тот же ответ приходит для неизвестного, отозванного или истёкшего ключа: существование ключа не раскрывается. На странице это выглядит так, будто автосбор просто «не настроен».

Что делать:

  1. Во вкладке «Сеть» откройте неудачный запрос на /v1/track и посмотрите заголовок запроса Origin — это точное значение, с которым идёт сравнение.
  2. Сравните его со столбцом «Scopes / origins» в списке ключей проекта.
  3. Если адрес отсутствует — выпустите новый токен с полным списком и замените значение. Отредактировать список у существующего токена нельзя.

Проверка одна и та же для всех запросов браузерного токена: /v1/identify, запись сессий и захват элемента ответят тем же 403, если origin не в списке.

Ответ 403 с кодом credential_scope_forbidden — про другое: это несовпадение типа ключа с запросом, а не origin. Разбор кодов по симптомам собран в дереве диагностики.

Почему серверному ключу это не нужно

Проверка origin применяется только к браузерному токену. У запроса с бэкенда заголовка Origin нет вовсе, и CORS к нему не относится: это не браузер. Границей доверия для серверного ключа служит его секретность, а не адрес источника.

Отсюда два практических следствия:

  • если запрос с бэкенда получил credential_origin_forbidden, значит там используется браузерный токен — замените его на серверный ключ;
  • перенести серверный ключ в браузер, «чтобы не настраивать origin», нельзя. Это не обход ограничения, а утечка: ключ станет виден каждому посетителю.

Политика безопасности контента

Если на сайте настроен Content-Security-Policy, разрешите три вещи:

Директива Значение Кому нужно
script-src origin, с которого загружается файл трекера загрузка ядра и дополнительных модулей
connect-src origin приёма событий отправка событий, в том числе на выходе со страницы
worker-src blob: — и data: для браузеров без blob-воркеров только записи сессий
script-src 'self' https://YOUR_ACTIONPULSE_HOST;
connect-src 'self' https://YOUR_ACTIONPULSE_HOST;
worker-src 'self' blob:;

Если файлы вы отдаёте со своего домена, в script-src достаточно 'self', а connect-src всё равно должен содержать адрес приёма.

Директива unsafe-eval не нужна ни при каких условиях: в бандлах нет ни eval, ни new Function.

Дополнительные модули загружаются с origin самого файла трекера, а не с адреса приёма событий: в script-src нужен именно тот домен, с которого приходит ядро.

Новую политику удобно сначала выкатить в режиме Content-Security-Policy-Report-Only и посмотреть отчёты, а не выключать сбор целиком.

Что проверить

  1. Откройте страницу, подтвердите согласие и найдите во вкладке «Сеть» запрос на /v1/track: ожидаемый ответ — 202.
  2. Проверьте предварительный запрос OPTIONS на тот же адрес: 204. Если его нет, браузер использует результат предыдущей проверки — он кэшируется на 10 минут.
  3. Найдите запрос GET /v1/cfg и посмотрите тело: если переключатели сбора включены в настройках проекта, а в ответе всё выключено, — origin не совпал.
  4. Сверьте заголовок Origin запроса со списком у токена; проверьте варианты с www и без него.
  5. Убедитесь, что в консоли нет сообщений про Content-Security-Policy, и отдельно проверьте записи сессий — они ломаются раньше остального.