Главная / Статьи / Стратегия токенов обновления для систем аутентификации Node.js

Стратегия токенов обновления для систем аутентификации Node.js

Узнайте, как проектировать, поворачивать, аннулировать и безопасно хранить токены обновления в Node.js, чтобы кража токенов и выход из системы действительно работали так, как ожидается.

2681 слов

Когда в проекте Node.js впервые настраивается процесс входа, может показаться, что работа практически завершена за короткое время.

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

Но при более внимательном рассмотрении выявляется длинный список вопросов, на которые этот простой процесс так и не отвечает:

  • Что происходит, когда токен истекает?
  • Должен ли пользователь каждый раз заново входить?
  • Если токен украдут, как долго злоумышленник сможет им пользоваться?
  • Как на самом деле работает процесс выхода пользователя?
  • Существует ли способ аннулировать уже выданный токен?
  • Что произойдет, если украденный токен снова будет использован даже после того, как он предположительно был аннулирован?
  • Где вообще следует хранить такой токен на стороне клиента?
  • Ответов на эти вопросы нет в формулировке «подписать JWT и проверить его позже». Обычно именно здесь необходимо отказаться от готовых уроков по входу в систему и серьезно изучить принцип работы токенов обновления.

    Почему токены доступа не действуют долго

    Краткосрочный срок действия токена доступа — это не значение по умолчанию, которое кто-то забыл изменить, а осознанный дизайнерский выбор.

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

    Однако такой выбор дизайна создаёт собственные проблемы. Если токен истекает каждые 15 минут, должен ли пользователь вводить свой пароль заново каждые 15 минут? Очевидно, что это нереалистично. Токены обновления существуют именно для устранения этой проблемы.

    Так что же на самом деле делает токен обновления?

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

    Процесс обычно выглядит следующим образом:

    1. Пользователь входит в систему.
    2. Сервер возвращает как токен доступа, так и токен обновления.
    3. Токен доступа используется для отправки запросов к API.
    4. В конечном итоге токен доступа истекает.
    5. Клиент отправляет токен обновления на специальный конечный пункт обновления.
    6. Если сервер успешно его верифицирует, он выдаёт совершенно новый токен доступа.

    Концепция, которую стоит усвоить, заключается в том, что токен обновления никогда не предназначен для прямого обращения к конечным точкам вашего API. Его единственная функция — получение нового токена доступа. Как только эти две задачи будут четко разделены в мышлении, остальная структура решения начнет логически складываться.

    Токен доступа против токена обновления

    Токен доступа используется для взаимодействия с защищенными API, тогда как токен обновления применяется исключительно для получения нового токена доступа. Токен доступа имеет короткий срок действия, а токен обновления — более длительный. Токен доступа передается практически с каждым запросом, в то время как токен обновления используется лишь периодически. Утечка токена доступа — это проблема, но утечка токена обновления еще хуже, поскольку с его помощью можно продолжать генерировать новые токены доступа. Токен доступа проверяется при каждом вызове API, а токен обновления — только с помощью специальной логики обновления или управления сессиями.

    Часто упоминаемое разделение — это срок жизни токена доступа в 15 минут и срок жизни токена обновления в 7 дней, но в этих цифрах нет ничего магического. Подходящие значения полностью зависят от того, что могут выдержать ваше приложение и ваши пользователи.

    Почему токены обновления заслуживают большего уважения, чем кажется на первый взгляд

    Токен обновления может эффективно поддерживать сессию активной столько, сколько он остается действительным. Если злоумышленник получит его, у него появляется не просто один период доступа — он может продолжать генерировать новые токены доступа до истечения срока действия или аннулирования токена обновления.

    Эта реальность меняет подход к обращению с токеном. Это не просто еще один фрагмент данных приложения; он ведет себя скорее как настоящий ключ от дома. Обращение с ним именно так подразумевает необходимость учитывать следующее:

    • истечение срока действия
    • аннулирование
    • замену
  • где и как оно хранится
  • как оно транспортируется
  • обнаружение повторного использования
  • что на самом деле должно делать выход из системы
  • управление сессиями в целом
  • Обычно именно здесь кажущаяся простой настройка JWT начинает показывать свои слабые места.

    Обновление токенов: недопущение бесконечного существования одного токена

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

    Вместо одного долговечного учетного данных, циркулирующего вечно, у вас появляется цепочка токенов, при которой каждый заменяет предыдущий:

    Используется токен A, в результате чего выдается токен B, пока A находится в резерве. Затем используется токен B, что приводит к выдаче токена C, пока B находится в резерве. Эта схема продолжается бесконечно, причем действительным остается только самый последний токен в цепочке.

    Почему это действительно помогает

    Рассмотрим ситуацию, когда злоумышленник каким-то образом получает копию токена A, в то время как законный пользователь все еще им владеет.

    Если настоящий пользователь случайно использует его первым, токен A отмечается как использованный и фактически становится недействительным, а вместо него выдается токен B. Теперь, когда злоумышленник пытается использовать этот же токен A позже, сервер распознает его как токен, который уже был использован, что является явным тревожным сигналом. В зависимости от степени строгости настройки системы такое обнаружение может привести к аннулированию всей цепочки токенов, связанных с этой сессией, а не только одного скомпрометированного токена.

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

    Аннулирование, потому что выход из системы действительно должен иметь значение

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

    Если пользователь нажимает «Выход», и единственное, что происходит, — это удаление токена с фронтенда, сам токен остается полностью действительным до момента естественного истечения срока его действия. Сервер не осознаёт, что пользователь «вышел из системы» в каком-либо значимом смысле, поэтому он будет продолжать принимать тот же токен, если тот снова будет предоставлен до истечения срока его действия.

    Токены обновления предоставляют реальный механизм для отслеживания и прерывания сессий с серверной стороны. До момента выхода из системы токен сессии находится в активном состоянии. После выхода из системы этот токен должен быть помечен как аннулированный, и любые последующие попытки использовать его для обновления должны проваливаться. Именно так выглядит настоящий выход из системы, а не лишь формальный процесс.

    Выход из системы должен выполняться на бэкенде, а не только на фронтенде

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

    Более полный и корректный процесс выхода из системы обычно следует примерно такой последовательности:

    1. Клиент отправляет запрос POST /auth/logout
    2. Сервер определяет, к какой сессии относится этот запрос
  • Сервер аннулирует токен обновления или сессию, связанную с ним
  • Только тогда клиент очищает свое собственное локальное состояние
  • Ключевой момент, который стоит подчеркнуть, заключается в том, что процесс выхода из аккаунта по сути является событием безопасности на стороне сервера. Если ваша реализация выхода из аккаунта затрагивает только данные, хранящиеся на клиенте, то на самом деле пользователь не вышел из аккаунта — вы просто заставили интерфейс забыть о существовании этого пользователя.

    Определение места хранения токена

    Выбор способа хранения имеет большее значение, чем кажется на первый взгляд. Для приложений в браузере распространенным подходом является хранение токена обновления в куки, настроенной с несколькими определенными атрибутами:

    • HttpOnly — предотвращает прямое чтение токена с помощью JavaScript на стороне клиента
    • Secure — ограничивает передачу токена только по протоколу HTTPS
  • SameSite — параметр, снижающий риск некоторых категорий межсайтовых атак
  • Однако ни один из этих параметров не делает куки автоматически устойчивой к атакам. Вам по-прежнему необходимо учитывать меры защиты от CSRF, способ определения домена и пути, правила истечения сессии, а также взаимодействие процедуры выхода из аккаунта со всеми этими элементами. Не существует единственного шаблона хранения, который можно просто скопировать из учебного пособия и использовать без адаптации к архитектуре собственной системы.

    Что произойдет, если токен обновления все же утекнет

    Именно такой сценарий показал важность ротации токенов.

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

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

    Именно по этой причине токены обновления следует рассматривать как проблему в области безопасного проектирования, а не как что-то, что можно решить простым созданием ещё одного JWT.

    Токены обновления тоже должны истекать

    Это легко упустить из виду, но токены обновления также не должны быть вечными. Без срока истечения украденный токен обновления фактически превращается в «заднюю дверь», которая никогда не закрывается.

    Распространенным вариантом является срок действия токенов доступа в 15 минут и токенов обновления — 7 дней, хотя конкретные значения должны определяться в зависимости от вашей способности к риску, а не на основе данных из первого же учебного пособия, которое вы найдете.

    Стоит четко разделять в уме понятия срока действия токенов и сессии, поскольку это разные вещи. Сессия может оставаться активной в течение длительного времени благодаря постоянной замене токенов, хотя каждый отдельный токен существует лишь в течение короткого промежутка времени.

    Ошибки, которых стоит избегать

    • Токены доступа с чрезмерно длительным сроком действия, что увеличивает риски в случае их утечки
    • Токены обновления без какого-либо срока истечения, что создаёт постоянный путь к взлому
    • Полное игнорирование процедуры ротации токенов, из-за чего становится гораздо сложнее обнаружить кражу
    • Рассмотрение процедуры выхода из системы как чего-то, что происходит только на фронтенде
    • Небрежное обращение с конфиденциальной информацией, в результате чего токены попадают в логи, URL-адреса или хранилище на стороне клиента, где им не место
    • Игнорирование проверки повторного использования токенов, поскольку ротация без проверки на повторное использование на самом деле не обеспечивает значительной защиты

    Проверка процесса обновления токенов в реальных условиях

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

    1. Войдите и убедитесь, что вы получаете как токен доступа, так и токен обновления
    2. Обратитесь к защищенному маршруту с действительным токеном доступа и убедитесь, что вы получаете ответ 200 OK
    3. Обратитесь к защищенному маршруту с истекшим токеном доступа и убедитесь, что вы получаете ответ 401
    4. Обратитесь к точке обновления и убедитесь, что вы получаете новый токен доступа (а также новый токен обновления, если включена ротация)
    5. Попробуйте повторно использовать старый токен обновления, который уже был заменен, и убедитесь, что его отклоняют
    6. Выйдите из системы, затем попробуйте обновить данные с использованием уже недействительной сессии, и убедитесь, что это тоже отклоняется

    Выявление проблем на этом этапе гораздо дешевле, чем их обнаружение после запуска приложения.

    Полная картина

    Login
      ↓
    Access Token + Refresh Token issued
      ↓
    API requests using Access Token
      ↓
    Access Token expires
      ↓
    Refresh Token sent to refresh endpoint
      ↓
    Server validates session
      ↓
    Refresh Token rotated
      ↓
    New Access Token issued
      ↓
    API requests continue
    

    Если в любой момент процесса обновления возникает ошибка проверки — токен истек, аннулирован или просто недействителен — в ответ возвращается код 401, и пользователю приходится снова войти с нуля. Именно разделение между «кратковременным разрешением на вызов API» и «более длительным разрешением на оставание вошедшим» является основной идеей всей этой конфигурации, сформулированной в одном предложении.

    Чек-лист перед запуском в продакшене

    Дизайн токенов

    • Действительно ли токены доступа быстро истекают?
    • Имеют ли токены обновления собственный срок действия?
    • Ограничены ли токены обновления только конкретным эндпоинтом обновления, или их можно использовать с любыми маршрутами?
    • Не содержат ли данные токенов избыточной информации?

    Безопасность

    • Требуется ли использование HTTPS во всех случаях?
  • Хранятся ли токены обновления в безопасном месте?
  • Сохраняются ли секреты отдельно от кодовой базы?
  • Удаляются ли токены из логов?
  • Применяется ли защита от CSRF там, где это необходимо?
  • Обработка сессий

    • Можно ли отзывать токены обновления по требованию?
    • Происходит ли выход из аккаунта на сервере, а не только на клиенте?
    • Действительно ли реализовано обновление токенов?
    • Можно ли обнаружить повторное использование токена?

    Покрытие тестами

    • Действительный токен обновления приводит к успеху
    • Истекший токен обновления приводит к сбою
    • Отзыванный токен обновления приводит к сбою
    • Некорректный или недействительный токен обновления приводит к сбою
    • Ранее обновленный токен приводит к сбою
    • Правильный выход из аккаунта уничтожает соответствующую сессию

    В чем всё это сводится

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

    JWT упрощают процесс «проверки личности». Однако они не предоставляют бесплатно функции управления сессиями, аннулирования токенов, корректного выхода из системы, защиты от кражи токенов, обнаружения их повторного использования, безопасного хранения или разумных сроков истечения действия. Каждый из этих аспектов — это осознанный выбор, который вы должны сделать самостоятельно. Чем больше и чаще используется ваше приложение, тем важнее становятся эти решения.

    Что дальше

    Теперь, когда концепции токенов доступа и обновления стали понятнее, следующей областью, заслуживающей изучения, является управление сессиями в более масштабном формате:

    • Как следует обрабатывать ситуацию, когда пользователь остается вошедшим в систему на пяти разных устройствах?
  • Могут ли пользователи просматривать — и заканчивать — свои собственные активные сессии?
  • Возможно ли вывести кого-то из системы с одного устройства, не прерывая все его сессии?
  • Как выглядит «подозрительная» сессия и как её отметить?
  • Как правильно хранить семейства токенов обновления?
  • В какой момент модель сессий на основе базы данных становится более целесообразной, чем полностью безсостоянийный подход с JWT?
  • Сама реализация маршрута входа может занять десять строк кода. Однако создание системы аутентификации, которой можно действительно доверять, требует гораздо больше усилий.

    Связанные материалы

  • Почему декодирование кусков буфера как текста нарушает загрузку файлов — Объясняется, как обработка двоичных данных буфера как текста формата UTF-8 тайно повреждает загруженные файлы, и показано правильное обращение с данными на уровне байтов для предотвращения этого.
  • Сессии против JWT: как выбрать подходящую модель аутентификации в Node.js — Рассматриваются различия между сессиями и JWT в системах аутентификации Node.js, где проявляются их слабые стороны, а также способы выбора одного из вариантов без последующих проблем.
  • Реализация токенов доступа и обновления вместе в Node.js — Описывается, как сочетать краткосрочные токены доступа с периодически обновляемыми токенами обновления в Node.js для достижения баланса между безопасностью и бесперебойной работой сессий пользователей.