Головна / Статті / Стратегія токенів оновлення для систем автентифікації в 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 для досягнення балансу між безпекою та безперешкодними сеансами користувачів.