Параметры cookie SameSite: Secure, область домена и поддомены
Пояснение настроек SameSite cookie: как правильно выставить Secure, HttpOnly, область домена и поведение для поддоменов, чтобы логин надежно работал в продакшене.

Почему аутентификация ломается в продакшене, когда cookie настроены неверно
Когда логин работает на localhost, но падает на реальном домене, проблема часто не в проверке пароля или базе данных. Проблема — cookie.
Локальное тестирование прощает многое: всё на одном хосте, возможно используется HTTP, и редиректы проще. В продакшене браузеры применяют более строгие правила. Мелкие детали в настройке cookie решают, пришлёт ли браузер вашу сессию обратно на сервер.
Симптомы могут выглядеть нерелевантными:
- форма логина отправлена, но вы возвращаетесь на страницу входа
- цикл редиректов между
/loginи/app - сессии исчезают после обновления страницы
- в одном браузере всё работает, в другом — нет
В продакшене добавляются новые элементы: HTTPS, CDN или прокси, отдельные поддомены (например app.example.com и api.example.com) и редиректы через сторонние сервисы (OAuth, страницы оплаты, магические ссылки в письмах). Именно здесь начинают играть роль SameSite, Secure и область действия cookie.
Cookie — это не просто "токен сессии". Это ещё и набор правил, которые говорят браузеру, когда отправлять этот токен обратно.
Неправильная конфигурация обычно ломает аутентификацию несколькими предсказуемыми способами:
- браузер никогда не отправляет cookie в запросе, который создаёт или проверяет сессию (часто из-за SameSite или области домена);
- браузер отвергает cookie, потому что
Secureне установлен, когда это нужно; - cookie ограничена неправильным Domain или Path, и она не доходит до нужных маршрутов;
- в браузере существуют две cookie с одним и тем же именем (старая и новая, стейджинг и прод), и браузер отправляет другую, чем вы ожидаете.
Пример: всё работает на http://localhost:3000, но после деплоя на app.yourdomain.com ваш API ставит cookie-сессию только для api.yourdomain.com. Фронтенд её не отправляет, поэтому на каждой загрузке выглядит как разлогиненное состояние.
Основы cookie, важные для аутентификации
Большинство «входов в систему» по-прежнему полагаются на cookie, представляющую сессию. После ввода пароля сервер отправляет cookie в ответе, браузер сохраняет её, и при последующих запросах браузер прикрепляет эту cookie автоматически, чтобы сервер мог вас опознать.
Много путаницы возникает из-за смешения трёх связанных идей:
- Сессионная cookie: идентификатор, который сервер ищет (часто хранится в БД или кэше).
- Access token: краткоживущий маркер (часто используется для API).
- Refresh token: более долгоживущий маркер для получения нового access token.
Ошибки в продакшене часто возникают, когда приложение ожидает один подход, но реализует другой. Например, фронтенд ждёт cookie‑сессию, а бэкенд выдаёт токены для заголовка Authorization.
Для аутентификации через cookie атрибуты важны так же, как и само значение:
- SameSite: контролирует, когда cookie отправляется в кросс-сайтовых контекстах.
- Secure: cookie отправляется только по HTTPS.
- HttpOnly: блокирует доступ к cookie из JavaScript.
- Domain: какие хосты могут её получать.
- Path: какие URL‑пути могут её получать.
Браузеры не всегда отправляют cookie. Они отправляют их только когда домен и путь совпадают, соединение соответствует Secure, и контекст запроса проходит правила SameSite.
Классическая ошибка, проявляющаяся только в продакшене: локально всё — localhost, а в проде фронтенд на https://app.example.com, API — на https://api.example.com. Если Domain или SameSite хоть немного неверны, вы получите цикл логина.
SameSite: что он делает и когда блокирует ваш логин
SameSite — это флаг cookie, который говорит браузеру, когда ему разрешено отправлять вашу сессионную cookie. Многие проблемы «работает локально — ломается в проде» связаны с настройками SameSite, не соответствующими реальному потоку входа.
Lax vs Strict vs None (простыми словами)
SameSite=Strict — самый жёсткий. Cookie отправляется только в первом‑партийном (first-party) контексте. Если пользователь пришёл с другого сайта, браузер может не отправить cookie.
SameSite=Lax — значение по умолчанию, которое работает для многих приложений. Cookie обычно отправляется при обычной верхнеуровневой навигации (например, при клике по ссылке), но не при большинстве фоновых кросс-сайтовых запросов.
SameSite=None значит «разрешить эту cookie в кросс-сайтовых контекстах». Современные браузеры требуют, чтобы при None была установлена также Secure, иначе cookie отбросится.
Что обычно вызывает поломки
Слишком строгий SameSite часто ломает:
- OAuth‑логины (Google, GitHub), где пользователь перенаправляется назад;
- встраиваемые приложения (app внутри iframe на другом домене);
- потоки, где браузер считает фронтенд и API кросс-сайтовыми.
Дефолты браузеров тоже менялись. Cookie без явного SameSite могут трактоваться как Lax, а cookie с None без Secure обычно отклоняются.
Когда действительно нужен SameSite=None? Используйте его только если сессионная cookie должна отправляться в кросс-сайтовом контексте (iframe, настоящая кросс-сайтовая схема, некоторые редирект‑флоу). Если всё находится в пределах одного сайта, Lax часто достаточно и снижает риск.
Secure‑cookie и HTTPS: самая частая несостыковка в продакшене
Cookie с флагом Secure отправляется только по HTTPS. Это звучит просто, но именно из‑за этого многие логины работают локально, а потом рушатся после деплоя.
Правило, которое многих сбивает: если вы ставите SameSite=None, современные браузеры требуют, чтобы cookie также имела Secure. Без этого браузер может молча отбросить cookie.
Локальная разработка вводит в заблуждение. Многие приложения работают на http://localhost, поэтому Secure опускают для удобства. Затем в продакшене включают HTTPS, кто‑то ставит SameSite=None для редиректа или встраивания, и cookie просто не сохраняется.
Прокси и CDN могут усугубить ситуацию. Браузер у пользователя на HTTPS, но сервер приложения видит входящий трафик как HTTP, потому что TLS завершается на прокси. Если приложение решает «я на HTTP», оно может не ставить Secure или генерировать редиректы между HTTP и HTTPS.
Если cookie пропадают только на живом сайте, проверьте:
- в DevTools подтвердите, что у auth‑cookie установлен
Secureи URL запросаhttps://...; - ищите предупреждения вроде "cookie was blocked because it had SameSite=None without Secure";
- проверьте заголовки прокси (часто
X-Forwarded-Proto: https) и настройку доверия к прокси в вашем фреймворке; - убедитесь, что пользователи не попадают на HTTP‑версию сайта;
- подтвердите, что cookie выставляется на финальном домене после редиректов, а не на промежуточном хосте.
HttpOnly и CSRF: делаем cookie безопаснее, не ломая UX
Если сессионная cookie доступна в JavaScript, любая XSS‑уязвимость может привести к компрометации аккаунта. Поэтому HttpOnly важен. С HttpOnly браузер по‑прежнему отправляет cookie в запросах, но document.cookie её прочитать не сможет.
Хранение токенов в доступной JS cookie часто кажется удобным для SPA, но обычно это увеличивает риск без реальной пользы. Если фронтенду нужно знать, вошёл ли пользователь, вызывайте лёгкий эндпоинт вроде /me и пусть сервер решает на основании cookie.
CSRF — это вторая часть истории. Cookie отправляются автоматически, поэтому злой сайт может попытаться вызвать запросы от имени залогиненного браузера. SameSite помогает, но для изменяющих состояние запросов всё равно нужна защита CSRF.
Практические правила, которые упрощают UX и повышают безопасность:
- хранящую сессию держите только в
HttpOnlycookie (не в localStorage); - используйте защиту от CSRF для state‑изменяющих запросов;
- стесните область действия cookie (хост и путь), чтобы сессия отправлялась реже.
Одна особенность: если вы используете cookie для аутентификации API, SPA не может прикреплять их вручную. Браузер делает это сам, поэтому настройки CORS и опция credentials должны быть корректны.
Область действия Domain и Path: не давайте cookie уходить не туда
Баги с cookie часто выглядят как «случайные логауты» или «работает локально, но не после деплоя». Domain и Path решают, куда браузер отправит вашу аутентификационную cookie. Если они не совпадают с реальными URL, сервер просто не увидит cookie и посчитает пользователя неавторизованным.
Даже идеальные настройки SameSite не помогут, если cookie вообще не отправляется.
Host-only vs Domain cookie
Если не задавать атрибут Domain, получается host-only cookie. Она привязана к точному хосту, например app.example.com.
Если задать Domain=.example.com, cookie станет доступна субдоменам вроде app.example.com и api.example.com. Это удобно, но расширяет область, где cookie может быть отправлена, и повышает шанс перезаписи cookie с тем же именем на другом субдомене.
Path в мульти‑апп setups
Path — это URL‑"папка", где cookie действительна. Path=/ отправляет cookie на все пути хоста. Path=/app отправляет её только на /app и вложенные пути.
Распространённая ошибка — поставить cookie с Path=/api, потому что она создаётся на API‑роуте, а потом удивляться, почему страницы под /app не остаются залогиненными.
Быстрые проверки, которые предотвращают много проблем в продакшене:
- выберите один канонический хост (
wwwили безwww) и придерживайтесь его; - если нужна общая аутентификация по субдоменам, используйте cookie родительского домена. Иначе оставьте
Domainнеустановленным; - используйте
Path=/, если нет явной причины ограничивать путь; - избегайте повторного использования одного и того же имени cookie в разных приложениях с разными областями;
- проверьте, какой хост фактически ставит cookie после редиректов.
Поддомены: предсказуемое поведение cookie для app и API
Типичная конфигурация — app.example.com для фронтенда и api.example.com для бэкенда. На первый взгляд всё просто, но правила cookie могут отличаться между локальной разработкой и продакшном, как только появляются HTTPS, прокси и реальные домены.
Если cookie host-only, то cookie, поставленная api.example.com, не будет отправлена на app.example.com и наоборот. Если вам нужна одна cookie на обоих, обычно её настраивают на родительский домен.
Прежде чем делать шаринг cookie по поддоменам, уточните:
- действительно ли вам нужна одна и та же cookie на обоих поддоменах, или API может быть единственным местом, где устанавливают и читают сессию?
- настроен ли у вас HTTPS на всём пути, включая балансировщик нагрузки или CDN?
- соответствуют ли ваши настройки SameSite тому, как браузер классифицирует запросы (same-site против cross-site)?
Также помните: CORS и cookie должны согласовываться. Даже если cookie правильно скоуплена, браузер не прикрепит её к запросу API, если fetch/XHR не использует credentials и API не разрешает credentials с конкретным Origin.
Шаринг cookie по поддоменам не всегда оправдан. Широкая область cookie увеличивает количество мест, куда она может быть отправлена. Держите аутентификационные cookie как можно более ограниченными и расширяйте область только при явной необходимости.
Пошагово: безопасная конфигурация cookie для современных браузеров
Безопасная настройка cookie начинается с реального пользовательского сценария, а не из дефолтных настроек фреймворка. То, что работает на localhost, может не пройти после деплоя из‑за HTTPS, редиректов и отдельных хостов.
Практический чеклист
Сопоставьте ваш поток логина от начала до конца и сделайте cookie соответствующей:
- Нарисуйте реальный поток логина (включая редиректы). Запишите каждый шаг: приложение, провайдер аутентификации, callback URL, дашборд.
- Решите, где жить сессии. Выберите одно место для установки и чтения сессионной cookie (часто это API).
- Выберите SameSite в зависимости от потока. Используйте
Noneтолько если сессия действительно должна отправляться в кросс‑сайтовых ситуациях. ИначеLaxчасто достаточно. - Принудительно включите HTTPS и установите
SecureиHttpOnly. Если вы используетеSameSite=None,Secureобязателен. - Устанавливайте
DomainиPathосознанно. Расширяйте область только при необходимости. ДержитеPath=/, если нет явной причины ограничивать.
Проверяйте на реальном продакшен‑домене в режиме инкогнито: залогиньтесь, сделайте жёсткое обновление, откройте новую вкладку и подтвердите, что cookie присутствует и отправляется в тех запросах, которые важны.
Распространённые ошибки, приводящие к трудноотлавливаемым багам аутентификации
Баги с cookie кажутся случайными, потому что обычно проявляются только после деплоя. Небольшие различия (HTTPS, реальный домен, прокси, отдельный API хост) заставляют браузерные правила иметь значение.
Ошибки за большинством циклов логина и «вышел после обновления»:
- поставили
SameSite=None, но забылиSecure(браузер может отбросить cookie); - выставили
Domainслишком широко и нечаянно делитесь cookie с другими субдоменами; - смешали HTTP и HTTPS за прокси, и приложение ставит cookie или редиректы исходя из неверной схемы;
- доверяете поведению localhost, которое не совпадает с реальными доменами;
- считаете, что CORS — единственная проблема. CORS может разрешить запрос, но cookie всё ещё могут блокироваться из‑за области или SameSite.
Пример: вы деплоите app.example.com (фронтенд) и api.example.com (бэкенд). Вы меняете SameSite, чтобы разрешить кросс‑сайтовое поведение, но забываете Secure. В продакшене браузер отбрасывает сессионную cookie, и каждый рефреш выглядит как новый пользователь.
Быстрые проверки перед релизом (и при получении отчёта о баге)
Большинство багов с cookie становится очевидно, как только вы проверите три места: хранилище cookie в браузере, сетевой запрос и серверную причину отказа сессии.
В DevTools (Application/Storage) кликните cookie и проверьте:
- появилась ли cookie после логина и сохраняется ли после обновления?
- соответствуют ли
SameSite,Secure,HttpOnly,DomainиPathвашим ожиданиям? - выглядят ли
ExpiresилиMax-Ageадекватно? - случайно ли вы не установили две cookie с одинаковым именем на разных доменах?
- какой хост реально устанавливает cookie (app vs api)?
Затем проверьте Network: откройте аутентифицированный запрос (например GET /me). Если cookie есть в хранилище, но не в запросе, то либо атрибуты её блокируют, либо запрос идёт на другой хост, чем вы думаете.
Наконец, подтвердите HTTPS end‑to‑end. Если TLS завершается на балансировщике, но отсутствуют прокси‑заголовки, приложение может ставить не‑Secure cookie или генерировать редиректы, которые ломают сессию.
По возможности логируйте на стороне сервера понятную причину отказа сессии: отсутствующая cookie, истёкшая сессия, неверная подпись, CSRF‑ошибка и т. п.
Пример: цикл логина после деплоя из‑за SameSite и домена
Обычная история: OAuth‑логин работал на localhost, а после деплоя на https://app.example.com ломается. Пользователь кликает «Continue with Google», возвращается, на мгновение кажется залогиненным, а затем снова на страницу логина. И так по кругу.
Обычно это несоответствие cookie при OAuth‑редиректе и между поддоменами.
Локально приложение и API могли быть оба на localhost, поэтому браузер отправлял cookie везде. После деплоя поток выглядит примерно так:
-
Провайдер OAuth редиректит пользователя на
https://app.example.com/auth/callback. -
Callback ставит сессионную cookie, но она host‑only для
app.example.com(без атрибутаDomain). -
Фронтенд вызывает
https://api.example.com/me, чтобы получить данные пользователя. -
Браузер не отправляет cookie на
api.example.com, поэтому API возвращает 401. Приложение считает, что пользователь не залогинен, и заново запускает поток логина.
SameSite может усугубить ситуацию. Если cookie, используемая в OAuth callback, имеет SameSite=Strict, браузер может не отправить её при кросс‑сайтовом редиректе от провайдера, и сервер не сможет сопоставить state — поток провалится.
Практический план исправления:
- Установите
Secure=trueв продакшене (особенно если вы используетеSameSite=None). - Используйте
SameSite=Laxдля типичных сессионных cookie и OAuth‑state cookie (обычно проходит при top‑level редиректах). - Если API действительно должен видеть ту же cookie, скоупьте её на родительский домен и держите
Pathсогласованным.
Чтобы подтвердить исправление без догадок:
- Посмотрите запись cookie и проверьте
Domain,Path,SameSiteиSecure; - В сломанном запросе API проверьте, есть ли заголовок
Cookie; - Ищите заметки о блокировке в панели cookie (браузеры часто объясняют, почему cookie отклонена).
Следующие шаги: зафиксируйте настройку и позовите на помощь, если всё ещё ненадёжно
Когда вы найдёте набор настроек cookie, которые работают, задокументируйте их как правило, а не как догадку: точные значения для SameSite, Secure, HttpOnly, Domain и Path, плюс различия между локалом, стейджингом и продакшеном.
Держите небольшой тест‑план, который можно прогонять перед релизами: логин, обновление, новая вкладка, инкогнито и второй браузер (Chrome и Safari часто быстро выявляют различия).
Если вы работаете с AI‑сгенерированным прототипом, который ломается на реальных доменах, чаще всего виноваты: отсутствие Secure, несоответствие Domain/Path, cookie устанавливается неправильным хостом после редиректа или кросс‑сайтовое поведение, которое блокирует браузер.
Если хотите второго мнения, FixMyMess (fixmymess.ai) фокусируется на том, чтобы брать сломанные AI‑сгенерированные приложения и делать их готовыми к продакшену, включая диагностику потоков аутентификации и cookie от начала до конца. Быстрый аудит обычно достаточно, чтобы указать точный атрибут или шаг редиректа, который ломает сессию.
Часто задаваемые вопросы
Почему вход работает на localhost, но ломается после деплоя?
Обычно браузер просто не отправляет сессионную cookie на ваш продакшен-домен. На localhost всё происходило на одном хосте, а в продакшене HTTPS, поддомены и редиректы заставляют браузеры применять более строгие правила, поэтому чуть неверные SameSite, Secure, Domain или Path могут заставить сервер думать, что пользователь не вошёл в систему.
Что вызывает цикл редиректов между "/login" и "/app"?
Цикл редиректов часто означает, что во время логина приложение ставит cookie, но следующий запрос, который проверяет сессию, эту cookie не включает. Чаще всего причина — cookie выставлена для неправильного хоста (app vs API поддомен), SameSite блокирует её во время редиректа, или cookie отбрасывается потому что использует SameSite=None без Secure.
Какую настройку SameSite выбрать для большинства аутентификационных cookie?
Начните с SameSite=Lax для типичной сессии веб-приложения: он обычно проходит при обычной навигации верхнего уровня и многих OAuth-редиректах. Используйте SameSite=None только если вам действительно нужно отправлять cookie в кросс-сайтовых контекстах (iframe или настоящая кросс-сайтовая схема) и только вместе с Secure.
Почему `SameSite=None` требует `Secure`?
Современные браузеры отвергают cookie с SameSite=None, если у них нет флага Secure. Это означает, что такие cookie должны передаваться только по HTTPS. Если забыть Secure, браузер может выглядеть так, будто сервер установил cookie, но сам браузер молча её отбрасывает — и при обновлении страницы пользователь кажется вышедшим из системы.
Почему Secure-cookie ломаются за CDN или обратным прокси?
Secure означает, что cookie посылается только по HTTPS. В продакшене это правильно, но прокси и CDN могут скрывать от вашего приложения, что исходный запрос был по HTTPS (TLS мог завершиться на прокси). Тогда бэкенд может мыслить, что запрос пришёл по HTTP, и вести себя по-другому: не ставить Secure или генерировать неверные редиректы. Обычно решение — гарантировать HTTPS и корректно настроить доверие к заголовкам прокси.
В чём разница между host-only cookie и Domain-cookie?
Host-only cookie (без атрибута Domain) отправляется только на тот хост, который её поставил — например api.example.com. Домейн-cookie (например, .example.com) доступна для субдоменов вроде app.example.com и api.example.com. Если фронтенд на app.example.com, а cookie выставлена как host-only для api.example.com, браузер не отправит её на app.example.com и пользователь будет казаться вышедшим из системы.
Как `Path` может привести к тому, что сессия «теряется» случайно?
Атрибут Path ограничивает, куда на том же хосте отправляется cookie. Если случайно выставить Path=/api, то страницы под /app не получат эту cookie, хотя находятся на том же домене, и проверки сессии будут падать. Для большинства аутентификационных cookie простым и безопасным выбором является Path=/.
Должна ли моя сессия храниться в HttpOnly-cookie, и не сломает ли это SPA?
Да — ставьте HttpOnly, чтобы JavaScript не мог читать сессионную cookie: это снижает риск при XSS. Если фронтенду нужно знать, вошёл ли пользователь в систему, сделайте вызов лёгкого эндпоинта вроде /me, и пусть сервер решает по cookie. Это держит состояние корректным и не выставляет секреты в клиентский код.
Как быстро отладить, устанавливается ли cookie и отправляется ли она?
Сначала проверьте, появляется ли cookie в браузере после логина и сохраняется ли после жёсткого обновления. Потом в Network-инспекторе посмотрите аутентифицированный запрос (например, GET /me) и подтвердите, что заголовок Cookie присутствует. Если cookie есть в хранилище, но не отправляется — почти всегда это SameSite, Secure, Domain, Path или запрос уходит на другой хост, чем вы думаете.
У моего AI-сгенерированного прототипа аутентификация нестабильна в продакшене — что делать дальше?
Если у вас прототип, сгенерированный AI, то настройки cookie и аутентификации часто непоследовательны между локалом и продакшеном, особенно при поддоменах, OAuth-колбэках и прокси. Самый быстрый путь — сфокусированный аудит реального потока логина на живом домене, чтобы найти конкретный атрибут или шаг редиректа, который теряет сессию. FixMyMess может просмотреть код и починить аутентификацию и cookie так, чтобы всё работало в продакшене, часто в течение 48–72 часов.