Статья A.S Groups

CORS для WordPress REST API: как безопасно открыть API внешнему фронтенду

CORS для WordPress REST API, внешний фронтенд и безопасные HTTP-заголовки

Навигация по статье

Услуги A.S Groups

Нужен сайт, магазин или автоматизация?

Помогаю бизнесу запускать и дорабатывать WordPress-проекты: от посадочной страницы до WooCommerce, CRM и Telegram-уведомлений.

Обсудить проект Telegram
WordPress под ключ Лендинги, корпоративные сайты и структура под заявки. WooCommerce Интернет-магазины, каталог, оплата, доставка и интеграции. Доработка сайта Правки, скорость, формы, баги и развитие текущего проекта. CRM / Telegram / AI Автоматизация заявок, уведомлений и ручных процессов.

Когда фронтенд работает на отдельном домене, браузер применяет политику same-origin. Даже если endpoint WordPress REST API открыт и отлично отвечает через curl или Postman, запрос из JavaScript может быть остановлен браузером из-за CORS.

Задача решается не отключением защиты и не универсальным Access-Control-Allow-Origin: *, а точной настройкой разрешённых origins, методов, заголовков и способа аутентификации. Важно понимать: CORS — это браузерный механизм контроля cross-origin запросов, а не система авторизации WordPress.

Как WordPress обрабатывает CORS по умолчанию

В ядре WordPress есть функция rest_send_cors_headers(). Она подключена к REST API через стандартные фильтры и отвечает за CORS-заголовки. Когда запрос содержит Origin, WordPress может вернуть Access-Control-Allow-Origin, разрешённые методы, credentials и Vary: Origin.

Это означает, что перед добавлением собственных заголовков через Nginx, Cloudflare или PHP нужно проверить, что уже отдаёт WordPress. Дублирующиеся или противоречивые CORS-заголовки часто создают проблему вместо её решения.

Почему запрос проходит в Postman, но падает в браузере

Postman и серверные HTTP-клиенты не обязаны применять браузерную CORS-политику. Поэтому ответ 200 в таком клиенте ещё не доказывает, что запрос допустит браузер.

Проверяйте три вещи отдельно:

  • доступен ли endpoint и возвращает ли ожидаемый HTTP-код;
  • есть ли корректные CORS-заголовки для конкретного Origin;
  • проходит ли preflight OPTIONS для методов и заголовков, которые собирается отправить фронтенд.

Что такое Origin

Origin включает схему, хост и порт. Для браузера https://app.example.com, https://www.example.com и http://app.example.com — разные origins.

Если приложение должно работать только с одного или нескольких известных адресов, лучше проверять их по белому списку. Не стоит отражать любой входящий Origin без валидации, особенно если endpoint работает с авторизованными данными.

Почему wildcard не подходит для credentials

Распространённая попытка исправления выглядит так:

Access-Control-Allow-Origin: *

Для публичного read-only API это иногда допустимо, но для запросов с cookies или credentials такой подход не подходит. Если браузер должен передавать авторизационные данные, серверу нужно вернуть конкретный разрешённый Origin и корректно обработать credentials.

Дополнительно важно отправлять Vary: Origin, чтобы reverse proxy или CDN не закэшировал ответ с заголовком для одного домена и не отдал его другому.

Preflight OPTIONS

Перед некоторыми cross-origin запросами браузер отправляет preflight: HTTP OPTIONS с заголовками Origin, Access-Control-Request-Method и иногда Access-Control-Request-Headers. Браузер проверяет, разрешает ли сервер будущий основной запрос.

Preflight часто появляется при POST/PUT/PATCH/DELETE, использовании Authorization, нестандартных заголовков или некоторых Content-Type.

WordPress REST API содержит обработку OPTIONS, но её могут нарушить WAF, reverse proxy, CDN, security plugin или кастомные правила сервера. Поэтому при ошибке сначала посмотрите фактический ответ OPTIONS, а не только JavaScript console.

Разрешённые request headers

WordPress предоставляет фильтр rest_allowed_cors_headers. Через него можно скорректировать набор заголовков, которые REST API разрешит в CORS-сценарии.

add_filter( 'rest_allowed_cors_headers', function ( $headers ) {
    $headers[] = 'Authorization';
    $headers[] = 'X-App-Version';
    return array_values( array_unique( $headers ) );
} );

Не добавляйте заголовки «на всякий случай». Список должен соответствовать реальным запросам приложения.

Как ограничить конкретные origins

Если нужен строгий контроль, удобнее реализовать его в одном месте: либо на уровне приложения, либо reverse proxy, но не распределять частичную логику между WordPress, Nginx и CDN.

Пример идеи для WordPress-плагина:

add_filter( 'rest_pre_serve_request', function ( $served ) {
    $origin = get_http_origin();
    $allowed = [
        'https://app.example.com',
        'https://admin.example.com',
    ];

    if ( $origin && in_array( $origin, $allowed, true ) ) {
        header( 'Access-Control-Allow-Origin: ' . esc_url_raw( $origin ) );
        header( 'Access-Control-Allow-Credentials: true' );
        header( 'Vary: Origin', false );
    }

    return $served;
} );

На практике нужно учитывать уже отправленные ядром заголовки, чтобы не получить два значения Access-Control-Allow-Origin. Поэтому production-реализацию следует строить после анализа текущего ответа сайта и используемой инфраструктуры.

CORS не заменяет permission_callback

Даже идеально настроенный CORS не защищает REST route от неавторизованного серверного запроса. Каждая кастомная REST route должна иметь корректный permission_callback и проверять права пользователя или другой механизм доступа.

Если endpoint меняет заказы, клиентов, настройки или другой чувствительный ресурс, нельзя считать проверку Origin полноценной авторизацией. Origin помогает браузеру контролировать cross-site доступ, но сервер должен самостоятельно решать, имеет ли вызывающая сторона право выполнить операцию.

Cookie authentication и nonce

Для запросов внутри обычной WordPress-сессии REST API использует cookie authentication. Официальная документация WordPress указывает, что REST-запросы из WordPress должны передавать nonce, обычно через X-WP-Nonce.

Это удобно для интерфейса внутри WordPress, но внешний SPA на другом домене требует аккуратной настройки cookies, SameSite, HTTPS, credentials и CORS. Если архитектура позволяет, зачастую безопаснее не передавать административную WordPress cookie напрямую внешнему браузерному приложению.

Application Passwords для внешних интеграций

Для программного доступа WordPress поддерживает Application Passwords. Они предназначены для API-аутентификации и должны использоваться только по HTTPS. Подробнее — в документации REST API Authentication.

Application Password не стоит встраивать в публичный JavaScript bundle: пользователь сможет увидеть секрет. Для SPA лучше использовать собственный backend/BFF, который хранит серверные credentials и обращается к WordPress от имени приложения.

Практику Application Passwords мы отдельно разбирали в материале WordPress REST API Application Passwords.

Пример архитектуры для headless WordPress

Для публичного каталога или контента схема может быть простой:

  • frontend запрашивает публичные GET endpoints;
  • WordPress разрешает только нужные origins либо API остаётся публичным для чтения;
  • изменяющие операции идут через backend приложения;
  • секреты хранятся только на сервере;
  • CDN кэширует публичный контент с корректным учётом Origin/Vary.

Для личного кабинета схема сложнее: нужно проектировать сессии, CSRF-защиту, refresh/token flow или backend proxy. Простое добавление CORS-заголовков эту задачу не решает.

Cloudflare, Nginx и reverse proxy

Даже если WordPress отвечает правильно, промежуточный слой может изменить результат. Проверьте:

  • не блокируется ли метод OPTIONS;
  • доходит ли заголовок Origin до WordPress;
  • не удаляется ли Authorization;
  • не добавляет ли прокси второй Access-Control-Allow-Origin;
  • сохраняется ли Vary: Origin;
  • не кэшируются ли preflight-ответы с неверной политикой.

Если CORS реализован на edge-уровне, WordPress не должен одновременно формировать конфликтующую политику.

Как диагностировать CORS через curl

Сначала проверьте preflight:

curl -i -X OPTIONS 'https://example.com/wp-json/wp/v2/posts' \
  -H 'Origin: https://app.example.com' \
  -H 'Access-Control-Request-Method: GET' \
  -H 'Access-Control-Request-Headers: Authorization, Content-Type'

Затем основной запрос:

curl -i 'https://example.com/wp-json/wp/v2/posts' \
  -H 'Origin: https://app.example.com'

Повторите тест с разрешённым и запрещённым Origin. Смотрите не только HTTP-код, но и фактические Access-Control-* и Vary.

Типовые ошибки

  • Два Access-Control-Allow-Origin. Один добавляет WordPress, второй Nginx или CDN.
  • Wildcard плюс credentials. Браузер не принимает такую комбинацию.
  • OPTIONS блокируется WAF. Основной endpoint работает, но браузер до него не доходит.
  • Authorization отсутствует в allowed headers. Preflight завершается ошибкой.
  • Секрет находится во frontend. CORS не скрывает Application Password или API key от пользователя.
  • Нет permission_callback. Разрешённый Origin ошибочно воспринимается как контроль доступа.
  • CDN не учитывает Vary. Клиенты получают заголовок, закэшированный для другого Origin.

Чек-лист безопасной настройки

  1. Зафиксировать список frontend origins.
  2. Определить публичные и авторизованные endpoints.
  3. Выбрать способ аутентификации отдельно от CORS.
  4. Проверить стандартные CORS-заголовки WordPress.
  5. Разрешить только реально используемые methods и headers.
  6. Проверить OPTIONS на всех промежуточных слоях.
  7. Проверить permission_callback и capabilities.
  8. Не хранить серверные API-секреты в JavaScript.
  9. Проверить кэш и Vary: Origin.
  10. Протестировать разрешённый и запрещённый Origin.

Для сложных интеграций важнее не «убрать ошибку CORS», а построить предсказуемую границу между WordPress и внешним приложением. A.S Groups занимается интеграциями WordPress через REST API и webhooks, настройкой авторизации и серверной логики. Если внешний фронтенд уже есть, можно прислать схему доменов, endpoints и текущую ошибку для диагностики.

Следующий шаг

Нужно решить похожую задачу?

Предложить настройку WordPress REST API, headless-интеграций, CORS, авторизации и безопасного обмена данными с внешним фронтендом.

Обсудить задачу

Источники

Обсуждение

Вопросы и комментарии

Можно уточнить детали статьи или поделиться своим опытом. Первый комментарий проходит проверку.

Оставить комментарий

Email не публикуется. Ссылки и HTML в тексте удаляются.

Мы используем приватную аналитику SlimStat, чтобы понимать, какие страницы полезны посетителям, и улучшать сайт. IP-адреса анонимизируются и хэшируются. Вы можете согласиться или отказаться от аналитики.
Cookies и конфиденциальность

Используем необходимые cookies, аналитику и данные форм, чтобы сайт работал корректно и заявки доходили.