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

Проблема: два входа в одном приложении и пользователи в подвешенном состоянии
Две отдельные системы входа создают странное промежуточное состояние для пользователей. Они входят через одну форму, а потом их перебрасывает на другой экран входа. В локальных тестах всё может выглядеть нормально, а в реальности пользователи жалуются на случайные разлогины, отсутствие доступа или бесконечные циклы перенаправлений.
Подсказки обычно непримечательны, но очевидны при внимательном взгляде: два разных экрана входа, два набора cookie с похожими именами и смешанные проверки аутентификации, которые иногда ищут cookie сессии, а иногда ожидают JWT. Во вкладке сети вы можете увидеть, что один endpoint ставит cookie вроде session, а другой — что-то вроде auth_token.
Это часто встречается при быстром прототипировании и AI‑сгенерированном коде. Инструмент добавляет готовый пакет аутентификации, затем позже кто-то дописывает кастомный вход "пока так", или шаблон фреймворка сливается с существующим API. Ничто не заменяет старый путь полностью, поэтому оба остаются активными.
Чаще всего ломается не форма входа, а «трубопровод» вокруг неё:
- Сброс пароля обновляет одну таблицу пользователей, а приложение читает другую.
- Подтверждение email выполняется в одном потоке, но пропускается в другом.
- Роли и права проверяют разные клаймы или разные колонки в БД.
- "Запомнить меня" сессии не совпадают с middleware, которое защищает страницы.
Риск в том, что удаление неправильного куска кода может заблокировать реальных пользователей без предупреждения. Вы удаляете «дубликат» cookie и думаете, что всё почистили, но именно эта cookie могла быть единственным способом поддерживать старые сессии. Или вы удаляете «неиспользуемую» таблицу сессий и обнаруживаете, что фоновые задания (или мобильный клиент) зависели от неё для обновления токенов.
Относитесь к консолидации как к миграции пользователей, а не к уборке кода. Цель — один источник правды, с контролируемым перекрытием, чтобы существующие аккаунты и сессии продолжали работать.
Сделайте чёткий инвентарь того, что есть сейчас
Прежде чем что‑то менять, перечислите все движущие части аутентификации. Большинство неудач при консолидации происходят потому, что какая‑то невидимая проверка (middleware, API guard, edge‑функция) всё ещё ожидает старую cookie или формат сессии.
Назовите два подхода простыми словами, даже если код грязный. Например: "NextAuth cookie session" против "custom JWT в localStorage". Затем пропишите, за что отвечает каждая система: UI входа, доступ к API, записи в БД, админ‑страницы, фоновые задания и любые сторонние коллбэки.
Для каждой системы ответьте на вопросы:
- Что доказывает, что пользователь вошёл (имя cookie, заголовок, формат токена)?
- Где это проверяется (middleware, серверные маршруты, edge‑функции, обработчики API)?
- Какие данные она читает и пишет (таблица users, таблица sessions, refresh‑токены, роли)?
- Какие страницы или endpoints зависят от неё (админка, биллинг, загрузки, вебхуки)?
- Какие секреты и правила валидации вовлечены (JWT secret, OAuth ключи, хеширование паролей)?
Во время инвентаризации отметьте видимые риски продакшна: секреты в репозитории, проверки токенов, которые декодируют, но не проверяют, SQL, собранный из строк, и маршруты, обходящие проверки прав.
Как выбрать систему аутентификации, которую оставить
Цель — не выбрать "лучшую" в теории, а ту, которую безопасно эксплуатировать, проще поддерживать и которая меньше всего удивит вас в 2 часа ночи.
Начните с приоритетов. Для большинства команд безопасность и поддерживаемость важнее удобства. Если никто в команде не понимает одну из систем, это реальный риск, даже если она выглядит отполированной.
Далее подтвердите, какую систему реально используют пользователи. Не полагайтесь на UI. Проверьте логи входов, недавние записи в таблицах сессий и тикеты поддержки, где упоминают "не могу войти" или "снова разлогинило". Часто одна система обслуживает основной трафик, а другая — только крайние сценарии.
Наконец, подтвердите требуемые функции. Система, которую вы оставите, должна поддерживать то, что нужно приложению сегодня.
Короткая чек‑листа для выбора
Оставьте систему, которая покрывает ваши обязательные требования с наименьшим количеством кастомного кода:
- Методы входа (email/пароль, magic links, соцсети, SSO)
- Роли, команды и права (если они есть)
- Безопасные настройки по умолчанию (обращение с паролями, ротация токенов, CSRF где нужно)
- Чёткие границы в коде (одно место для создания сессий, одно место для проверки auth)
- Лёгкая отладка и понятные ошибки
Если вы в тупике, практическим критерием станет модель данных и границы. Вариант, который раскидывает проверки auth по случайным маршрутам, пишет в несколько таблиц «на всякий случай» и создаёт лишние cookie, будет продолжать порождать баги.
Пример: если фреймворк‑аутх надёжно работает с cookie сессиями, а кастомный вход дополнительно ставит собственную cookie и строку сессии, оставьте фреймворк и мигрируйте кастомный вход в него.
Поймите пользователей, сессии и модель данных прежде чем менять код
До консолидации проясните, что значит «пользователь» сейчас. Две системы часто создают две идентичности для одного человека, и приложение тихо переключается между ними. Пропуск этого шага ведёт к случайным разлогинам, потере данных и путанице с аккаунтами.
Проследите, как создаётся и сопоставляется запись пользователя. Привязки по email, внутреннему UUID, provider ID (Google/GitHub) или их смесь? Ищите крайние случаи: изменение email, разница в регистре (User@ vs user@), пользователи, зарегистрировавшиеся дважды разными способами.
Проверьте, где на самом деле хранятся пароли. Одна система может держать хэши паролей в таблице users, а другая вообще не хранить пароли (только социальный вход). Не предполагайте, что можно просто «перенести пароли» между системами — чаще всего нельзя и не нужно.
Затем промапьте сессии от начала до конца: что ставится в браузере, что хранится на сервере и что приложение проверяет при каждом запросе. Часто вы найдёте сразу несколько активных типов сессий.
Занесите на одну страницу снимок состояния:
- Идентификаторы пользователя: колонки и правила сопоставления
- Включённые методы аутентификации: пароль, magic link, OAuth провайдеры
- Механизм сессии: cookie, сессии в БД, JWT, refresh‑токены
- Где хранится состояние сессии: таблицы, кэш, localStorage
- Дублирующие таблицы: две таблицы users, две таблицы sessions, «теневые» таблицы профилей
Ищите «безобидные» дубликаты, которые таковыми не являются — например, таблица profiles, которая на самом деле источник прав или статуса подписки.
Спроектируйте миграцию так, чтобы существующие аккаунты продолжали работать
Целевое состояние — один источник правды для идентичности (кто это) и авторизации (что можно делать). Всё остальное — это слой совместимости, который можно убрать позже.
Решите, какая запись пользователя будет реальной. Эта запись должна владеть каноническим user ID, email и полями ролей/прав. Затем сделайте так, чтобы каждый путь входа разрешал тот же user ID, даже если запрос пришёл через устаревшую систему.
Если системы используют разные user ID, создайте маппинг. Самый безопасный подход — явный мост: сохраняйте старый ID на оставляемой записи пользователя или добавьте маленькую таблицу соответствий. Избягайте переписывания ID на месте, если они ссылаются в многих таблицах.
Пароли — место, где миграции чаще всего ломают пользователей. Если вы можете безопасно повторно использовать хэши паролей, скопируйте хэш вместе с нужной метаданной (алгоритм, соль, параметры) и оставьте старый верификатор на некоторое время. Заставляйте сброс только если действительно нельзя проверить старые хэши или если есть проблема безопасности.
Установите политики для типичных краевых случаев заранее:
- Дублирующиеся email: выбирайте «победителя» по последнему входу или по верифицированному email и карантинируйте другой.
- Отсутствие профилей: создавайте минимальные профили при первом успешном входе.
- Тестовые пользователи и начальные данные: помечайте и исключайте их из подсчётов миграции.
- Сиротские сессии: безопасно истекайте их, а не пытайтесь «чинить».
Пример: если у вас есть фреймворк‑auth и кастомный логин, оставьте таблицу пользователей фреймворка и импортируйте кастомных пользователей с полем old_id. Пользователи продолжают входить, а кастомный путь постепенно пенсионится.
Пошаговый план развёртывания, который избегает массовых разлогинов
Самый безопасный путь — короткое перекрытие, когда приложение умеет читать обе системы, затем контролируемая остановка записи в старую. Вы хотите, чтобы существующие сессии работали, а новые логины переходили в новый источник правды.
Начните с краёв: middleware, которые читают cookie, guards API и код поиска сессии. Именно там обычно появляются неожиданные разлогины.
Практичный план:
- Фаза 1 (compat): принимайте оба формата токенов/куки и мапьте оба на один внутренний user ID.
- Фаза 2 (тихая миграция): когда приходит запрос со старой сессией, перевыпустите новую сессию и установите новую cookie рядом со старой.
- Фаза 3 (переключение записи): создавайте новые сессии только в оставляемой системе. Прекратите запись в старую таблицу и перестаньте ставить старую cookie при логине.
- Фаза 4 (очистка): после понятной даты отсечения и мониторинга удалите старые пути, cookie и таблицы.
Между фазами наблюдайте реальные сигналы: успешность логинов, всплески 401/403, ошибки «user not found» и тикеты поддержки о разлогинах. Если что‑то идёт не так, откатите переключение записи (Фаза 3) прежде чем убрать чтение совместимости (Фаза 1).
Пример: если приложение ставит cookie фреймворка и кастомный JWT cookie, читайте оба в течение недели–двух, но после переключения только выдавайте cookie фреймворка.
Cookie и токены: как убрать лишнее без поломки сессий
Именно cookie и токены приводят к первым сбоям при консолидации. Одна система может использовать подписанную session cookie, другая — JWT плюс refresh‑токен. Пользователи оказываются залогиненными в одном месте и разлогиненными в другом.
Начните с перечисления каждой auth‑cookie и токена, которые ставит ваше приложение, и где они ставятся. Включите серверные middleware, клиентский код и хелперы фреймворка. Это единственный безопасный способ убрать дублирующие cookie.
Инвентарь должен включать:
- Имя cookie и её назначение (session, refresh, CSRF, "remember me")
- Кто её ставит (серверный маршрут, клиентский код, плагин фреймворка)
- Область видимости (домен, путь, SameSite, Secure, HttpOnly)
- Как она валидируется (ключ подписи, ключ шифрования, секрет токена)
- Где она читается (API, SSR‑страницы, edge‑middleware)
Коллизии cookie — частая скрытая проблема. Две системы могут переиспользовать одно и то же имя cookie с разными ключами, или ставить cookie в разных областях (app.example.com vs example.com). Это вызывает случайные разлогины, циклы перенаправлений или аутентификацию как чужая сессия.
Если обнаружите коллизию — запланируйте аккуратное переименование, а не попытки поддерживать обе навсегда. Введите новое имя cookie для системы, которую оставляете, принимайте обе коротко, а затем удалите старую.
Выход (logout) требует особой заботы в период перекрытия. Пользователь нажмёт "выйти" и ожидает, что всё очистится. Во время миграции реализация выхода должна удалять и старые, и новые cookie (а также отзывать refresh‑токены на сервере, если они используются). Иначе получите «призрачный» вход, когда старая cookie снова авторизует пользователя.
Пример: AI‑инструмент добавил NextAuth сессии, а потом кастомный JWT cookie auth. Если оба существуют, сервер может принимать NextAuth, а клиент продолжает слать JWT. Выберите один путь, при необходимости переименуйте оставляемую cookie и добавьте временный мост, который превращает валидную старую cookie в новую сессию.
Очистка БД: сессии, пользователи и оставшиеся таблицы аутентификации
Ошибки в БД — это то, как консолидация превращается в массовые разлогины или дыры в безопасности. Обращайтесь с очисткой как с миграцией, а не как с нажатием кнопки «удалить».
Спланируйте судьбу каждой auth‑таблицы:
- Keep: активно используемые системой таблицы (users, sessions, refresh tokens)
- Merge: содержат реальные данные пользователей, которые нужно перенести (profiles, emails, хэши паролей)
- Archive: полезны для поддержки и отката (legacy sessions, legacy accounts)
- Drop: действительно не используются после валидации
Перед изменениями в проде пишите обратимые миграции. Вместо удаления legacy‑строк сессий скопируйте их в архивную таблицу с отметкой времени, затем перестаньте читать legacy‑таблицу.
Сделайте ссылки скучными (и правильными)
Данные аутх не изолированы. Роли, членства в организациях, права и аудиторские логи часто ссылаются на конкретный user ID. Если новая аутх использует другие ID, нужен план перевода.
Простой подход — добавить стабильное поле маппинга (например, legacy_user_id) в оставляемую таблицу пользователей, мигрировать пользователей и затем обновлять ссылки небольшими батчами. То же для сессий: если были sessions и user_sessions, выберите один источник и адаптируйте код.
Последовательность развёртывания, уменьшающая сюрпризы:
- Бэктестве новые таблицы и поля маппинга без изменения поведения.
- Обновите приложение на чтение из нового источника, временно дуплируя запись.
- Проверьте соответствие ролей/организаций для реальных аккаунтов (включая админов).
- Переключите запись на оставляемые таблицы только.
- Архивируйте, затем удаляйте legacy таблицы после периода охлаждения.
Также убедитесь, что ничто больше не трогает старые таблицы: фоновые задания, админ‑панели, скрипты аналитики, инструменты поддержки и cron‑таски. Часто второй путь аутентификации прячется именно там.
Частые ошибки, которые разлогинивают пользователей или открывают дыры в безопасности
Самый быстрый способ получить волну тикетов поддержки — удалить старые куски до того, как новый путь обработает все реальные пользовательские сценарии. Приложение может выглядеть нормально в вашей сессии, но в проде у пользователей старые cookie, полусроки сессий и закладки.
Ошибки, приводящие к массовым разлогинам или уязвимостям:
- Отключение старого middleware слишком рано, из‑за чего существующие сессии перестают распознаваться.
- Изменение маршрутов сброса или верификации в середине миграции, что ломает ранее отправленные письма или подтверждает не тот аккаунт.
- Оставление проверок токенов слишком снисходительными (декодирование без валидации, пропуск issuer/audience, отсутствие истечения сессий).
- Забвение о других клиентах (мобильные приложения, админ‑инструменты, фоновые задания, входящие вебхуки), которые всё ещё шлют старую cookie или токен.
- Непроверка поведения cookie на разных поддоменах и окружениях (localhost vs staging vs production), из‑за чего cookie отбрасываются по домену, SameSite или Secure.
Конкретный пример: вы удаляете старую session cookie, потому что новая система использует JWT, но встроенный админ‑инструмент на admin.yoursite.com опирается на сессионную cookie. Он ломается, и кто‑то «чинит» это, отключая проверки авторизации на этом маршруте. Так миграции создают дыры.
Две меры безопасности снижают риск:
- Короткий период, когда активны оба валидатора, и лог, какой из них использовался для запроса.
- Заморозка путей для reset/verify до тех пор, пока старые письма не истекут и редиректы не проверены.
Быстрые проверки перед и после релиза
Самая безопасная очистка — та, которую можно быстро отменить. Изменения в коде — лишь половина риска. Вторая — что произойдёт в реальных браузерах со старыми cookie и полупротухшими сессиями.
Перед релизом убедитесь:
- Инвентарь полный: все имена cookie, заголовочные токены, хранилища сессий и auth‑таблицы задокументированы.
- Одна система — источник правды для идентичности и ролей, и все маршруты читают из неё.
- Имена cookie и их области подтверждены (домен, путь, SameSite, Secure) и есть план игнорировать или заменить старые cookie.
- Есть план отката, который один деплой возвращает (например, feature flag, который снова включает проверку старых сессий).
- Матрица тестов написана и запущена для регистрации, входа, выхода, сброса пароля и проверок ролей/прав.
Не полагайтесь на один «счастливый» аккаунт. Протестируйте хотя бы одного пользователя, созданного в каждой старой системе, плюс одного «грязного» пользователя, у которого установлены обе cookie. Там прячутся баги.
После релиза наблюдайте за поведением, а не только за кодом:
- Мониторьте ошибки входа по причинам (invalid token, missing session, CSRF mismatch, role denied), а не только общий счётчик.
- Сравнивайте скорость создания сессий с обычным трафиком.
- Подтвердите, что в старую таблицу сессий и legacy auth‑таблицы не поступают новые записи после cutover.
- Проверяйте потоки в инкогнито и в «грязном» браузере со старыми cookie.
- Когда всё стабильно, удаляйте старые секреты и ключи, очищайте старые сессии и только потом удаляйте оставшиеся таблицы.
Реалистичный пример: слияние фреймворк‑auth с кастомным входом
Частая AI‑сгенерированная ситуация: UI использует NextAuth (cookie, callbacks, таблица sessions), затем кто‑то добавляет кастомный JWT‑вход «только для API». В результате две точки правды для того, кто такой пользователь.
Симптомы запутывают. Пользователь может войти и смотреть UI, но вызовы API возвращают 401, потому что ожидают Bearer‑токен. Или API работает в Postman с JWT, а UI кидает на логин из‑за отсутствия cookie‑сессии. Хуже того, один и тот же email может получить два разных user ID в зависимости от того, какой путь создал аккаунт.
Самый безопасный фикс — выбрать победителя и мигрировать, не заставляя всех регистрироваться заново. Если вы консолидируете в NextAuth, временно держите кастомный JWT как слой совместимости.
Практичная миграция:
- Выберите единый real user ID (часто таблица пользователей NextAuth) и смапьте JWT‑только пользователей на него.
- Временно принимайте оба: API разрешает аутентификацию через cookie сессии или старый JWT, но оба резолвятся в один внутренний user ID в серверном коде.
- При следующем входе пользователя (или обновлении сессии) выдавайте только оставляемый метод сессии и перестаньте выкидывать новые JWT.
- После короткого окна удалите проверку JWT, удалите лишнюю cookie и пенсионите неиспользуемые таблицы сессий/токенов.
Безопасная очистка заканчивается одной cookie, одним хранилищем сессий и одним user ID во всех местах.
Следующие шаги: когда просить помощи
Если вы не можете объяснить в одной фразе, как запрос превращается в аутентифицированного пользователя в вашем приложении — остановитесь. Эти изменения могут выглядеть маленькими, но тихо блокировать людей или ослаблять безопасность.
Признаки, что нужно остановиться и привлечь второго взгляда
Вы уже в зоне "миграционного проекта", если:
- Нельзя сопоставить каждого пользователя с одной идентичностью (две таблицы пользователей, несовпадающие email, дублирующиеся ID).
- Вы не знаете, как хешируются пароли (или видите более одного метода хеширования).
- Проверки ролей смешаны (часть маршрутов через middleware, часть через кастомную cookie, часть через прямой запрос к БД).
- Сессии и cookie пересекаются (пользователи кажутся залогиненными, но API‑вызовы падают или идентичность меняется).
Что подготовить для ревью
Вы получите лучшее решение быстрее, если соберёте небольшой пакет фактов:
- Доступ к репозиторию (или чистый экспорт) и текущая конфигурация деплоя
- Список auth‑env‑переменных (имена cookie, JWT секреты, OAuth client IDs, callback URLs)
- Дамп схемы БД и счётчики строк для users, sessions и других auth‑таблиц
- Недавние логи по логину, обновлению токенов и неавторизованным ошибкам
- Короткая заметка о том, что сообщают пользователи (принудительные разлогины, не те аккаунты, сломанный доступ админов)
Если этот беспорядок создала AI‑утилита и вам нужен безопасный, продакшен‑ориентированный план очистки, FixMyMess (fixmymess.ai) помогает командам диагностировать запутанную аутентификацию, удалить дублирующие cookie и пути сессий и укрепить код до того, как что‑то сломается в продакшне.
Часто задаваемые вопросы
Как подтвердить, что у меня действительно две системы аутентификации?
Начните с перечисления всех точек входа для входа, cookie, токенов, таблиц сессий и проверок аутентификации. Во многих сломанных приложениях реальная проблема — не форма входа, а скрытый middleware и API-проверки, которые всё ещё ожидают старый формат.
Какую систему аутентификации мне оставить?
Оставьте ту систему, которая уже обслуживает большую часть реального трафика и имеет самые понятные и безопасные настройки по умолчанию. Если одна система — «самодельный клей», разбросанный по разным маршрутам, а другая даёт единый, предсказуемый поток сессий, центральная обычно безопаснее.
Почему я не могу просто удалить лишний вход и cookie?
Потому что удаление «дубликата» часто ломает существующие сессии и тихо блокирует пользователей. Обрабатывайте это как миграцию пользователей: сначала короткий период совместимости, где вы читаете оба формата, затем переключаете запись, а уже потом очищаете старое.
Как избежать создания двух идентичностей для одного человека?
Выберите каноническую запись пользователя и сделайте так, чтобы каждый путь аутентификации резолвился в один и тот же внутренний ID пользователя. Если системы используют разные ID, добавьте явный маппинг (например, поле old_id или таблицу соответствий) вместо массовой перезаписи ID во всех местах.
Могу ли я мигрировать пароли из старой системы в новую?
Чаще всего нет и не стоит пытаться, если вы не полностью понимаете старый формат хэша и связанные метаданные (алгоритм, соль, параметры). Безопасный подход — продолжать проверять старые хэши в переходный период или требовать сброс только когда действительно нельзя верифицировать старые учётные данные.
Как безопасно удалить лишние cookie и токены?
Сделайте явный инвентарь cookie и токенов: имена, области видимости, кто их ставит и кто их читает. Во время релиза принимайте оба формата краткое время, затем эмитируйте только оставляемую cookie и убедитесь, что выход удаляет и старые, и новые cookie, чтобы исключить «призрачные» входы.
Какой план раскатки предотвратит массовые разлогины?
Фазы: сначала чтение-совместимость, затем тихая перевыписка новых сессий при встрече старых, потом прекращение записи в старую систему и лишь затем удаление старых таблиц и кода. Переходите к следующей фазе только после стабильных метрик входа и отсутствия всплесков 401/403.
Что делать, если обе системы используют похожие имена или области cookie?
Переименуйте сохраняемую cookie в новое уникальное имя и принимайте старую временно. Коллизии случаются, когда две системы повторно используют одно имя cookie с разными ключами или разными scope (домен/путь), что вызывает циклы перенаправлений и случайные разлогины.
Как безопасно очистить дублирующиеся таблицы пользователей/сессий?
Не удаляйте сразу; архивируйте и убедитесь, что ничего уже не пишет в старые таблицы. Практичный подход: заполните новые таблицы и поля соответствия без изменения поведения, переключайте чтение, при необходимости временно дублируйте запись, затем переключите запись окончательно, архивируйте и лишь после периода охлаждения удаляйте устаревшее.
Когда стоит привлекать помощь (и что подготовить)?
Если вы не можете за одну фразу объяснить, как запрос превращается в аутентифицированного пользователя в UI, API и фоновых задачах — это уже проект миграции. Если это было создано AI и вам нужен быстрый, безопасный план, FixMyMess может провести бесплатный аудит кода и помочь объединить аутентификацию без поломок для пользователей.