Назад в блог

Авторизованный пользователь видит форму входа. Bitrix жив. Next.js жив. Они просто не договорились.

Когда мы перенесли каталог 28 000 товаров на Next.js, первые два дня после деплоя были про CSS и перфоманс. На третий день пришёл тикет: «Авторизованные пользователи видят форму входа вместо личного кабинета».

Bitrix был жив. Next.js был жив. Логи ничего не кричали. Но авторизованный пользователь с точки зрения фронтенда был никем.

Авторизация в headless Bitrix — это не одна проблема. Это три, которые появляются по очереди, когда фронт и бэк живут на разных доменах. Рассказываю по очереди.

Как Bitrix работает с сессией в монолите

В классическом стеке всё просто. Bitrix хранит сессию в $_SESSION, браузер получает PHPSESSID cookie с флагами httpOnly; SameSite=Lax. Каждый следующий запрос браузер отправляет этот cookie обратно — Bitrix его читает, достаёт пользователя из сессии, всё работает.

Дополнительно Bitrix использует BX_SESSID — антиCSRF-токен. На все POST-запросы через REST API нужен заголовок X-Bitrix-Csrf-Token с этим значением. Без него — 403. Это не документировано явно, но это так.

В монолите ты об этом не думаешь. Все запросы на одном домене, браузер сам несёт cookie, CSRF генерируется и отправляется незаметно.

Что происходит на разных доменах

Фронт: shop.example.com. Бэк (Bitrix API): api.example.com.

Разные домены — разные правила для cookie. Браузер по умолчанию не отправит PHPSESSID на api.example.com, когда запрос идёт с shop.example.com. Это SameSite-политика. Для кросс-доменных cookie нужен флаг SameSite=None; Secure — что, в свою очередь, требует HTTPS на обоих доменах и явного разрешения в настройках Bitrix.

Мы настроили CORS на Bitrix (Access-Control-Allow-Origin, Access-Control-Allow-Credentials: true), выставили SameSite=None, и клиентские fetch-запросы с credentials: "include" заработали.

Это закрыло половину проблемы.

BX_SESSID: тихий убийца POST-запросов

Личный кабинет отображался. Но любое действие пользователя возвращало 403. Добавить в корзину, сохранить адрес, оформить заказ.

Причина: BX_SESSID. Этот токен Bitrix генерирует при инициализации сессии и ожидает в заголовке X-Bitrix-Csrf-Token на каждый POST. В монолите он подставляется автоматически через PHP-шаблон. В headless нужно получить его явно: отдельный GET-запрос к Bitrix при инициализации сессии, потом передавать в каждый POST.

Схема: при логине получаем BX_SESSID из ответа Bitrix (поле result.SESSID), сохраняем в состоянии фронтенда или cookie, добавляем в заголовок каждого мутирующего запроса.

Звучит несложно. Но документация этого не описывает. Поняли из access.log: запросы без этого заголовка тихо возвращали 403, без объяснений.

Третья проблема: SSR без клиентского контекста

Next.js умеет делать запросы к API на стороне сервера — в getServerSideProps или в Server Components. Это хорошо для SEO и скорости первой загрузки.

Но SSR делает запрос к Bitrix без cookie браузера. Сервер Next.js не знает, кто залогинен. Если вы хотите рендерить персонализированный контент на сервере (корзина, wishlist, персональные цены), нужна дополнительная механика.

Три варианта.

Первый — проброс cookie. В getServerSideProps читаем cookie из входящего запроса (req.cookies) и передаём в заголовке Cookie на Bitrix. Работает, но хрупко: cookie пользователя проходит через серверный код, при SSR в Vercel нужно аккуратно с безопасностью.

Второй — JWT. При логине Bitrix выдаёт JWT-токен (через кастомный endpoint или OAuth2). Next.js хранит его в httpOnly cookie, на сервере и на клиенте работает через Bearer. Bitrix-side нужна собственная JWT-валидация. Трудоёмко, но чисто.

Третий — гибридный: CSR для авторизованного контента. Персонализированные части рендерятся на клиенте. Статический контент (каталог, описания) — SSR. Личный кабинет рендерится на клиенте после гидрации.

Что выбрали мы — и почему

На проекте 28k SKU мы выбрали гибридный подход с пробросом cookie через SSR для некритичных случаев и CSR для персонализированного контента.

Конкретно:

  • Каталог, описания товаров, категории — SSR без авторизации.
  • Корзина, личный кабинет, персональные цены — клиентский рендеринг после гидрации.
  • SSR с авторизацией оставили только для страниц, где SEO и персонализация пересекаются (их было ноль).

BX_SESSID получаем при инициализации сессии через отдельный GET, храним в памяти клиента, добавляем в заголовок.

Для SameSite=None потребовалась правка в настройках безопасности Bitrix (.settings.php) и явное добавление домена фронтенда в разрешённые CORS origins.

От JWT отказались: времени на реализацию кастомного endpoint не было, а стандартный OAuth2 у Bitrix рассчитан на внешние приложения, не на собственный фронтенд.

Что проверить перед запуском

Если у вас headless Bitrix + Next.js и есть авторизованные пользователи — вот рабочий чеклист:

  1. CORS на Bitrix настроен с credentials: true и явным Allow-Origin (не *).
  2. Cookie Bitrix выставлен с SameSite=None; Secure — и на Bitrix, и на обоих доменах есть HTTPS.
  3. При логине получаете SESSID из ответа и сохраняете на клиенте.
  4. POST-запросы через REST API несут заголовок X-Bitrix-Csrf-Token: <BX_SESSID>.
  5. SSR-запросы к Bitrix либо анонимные, либо с явным пробросом cookie из req.cookies.
  6. Проверьте личный кабинет в режиме инкогнито — без кэша браузера самая честная картина.
  7. Проверьте в браузере DevTools → Network → что cookie реально отправляется на Bitrix-домен (флаг Include у SameSite).

Три дня на то, чтобы разобраться в этом — это честная цена за то, что нигде не написано.

Следующий раз на предпроектной диагностике я спрашиваю: «Есть авторизованные пользователи в каталоге или это только для оформления?» Если да — закладываю ещё 3 дня в смету и явно объясняю клиенту, почему.


*Похожие задачи встречались при работе с API-контрактом между фронтом и Bitrix — про другие сюрпризы REST API читайте в Пять вещей, которые меня удивили в Bitrix REST API. Про то, что вообще оставить на Bitrix при headless — Что мы оставили на Bitrix.*