Навіщо два різні токени:
- токен доступу - короткоживучий (хвилини), надсилається з кожним запитом. Якщо його вкрадуть - шкода обмежена часом;
- refresh-токен - довгоживучий (дні, тижні), використовується лише для отримання нового токена доступу і лише на ендпойнті авторизації.
Це компроміс між безпекою (короткі токени доступу) і зручністю (користувач не вводить пароль щогодини).
Чому refresh-токен - найцінніша мішень: хто його має, той може безстроково отримувати нові токени доступу. Тому RFC 9700 (найкращі практики безпеки OAuth 2.0) вимагає для публічних клієнтів (SPA, мобільні застосунки) або прив'язки токена до клієнта (DPoP, mTLS), або ротації.
Ротація refresh-токенів: кожне оновлення видає новий refresh-токен, а старий стає недійсним.
refresh_1 → (access_2, refresh_2) refresh_1 недійсний
refresh_2 → (access_3, refresh_3) refresh_2 недійсний
Виявлення повторного використання - головна перевага ротації:
- зловмисник викрав
refresh_2і скористався ним першим - отримавrefresh_3; - легітимний клієнт пізніше пред'являє
refresh_2- вже використаний; - сервер розуміє, що один з двох - зловмисник, і відкликає всю сім'ю токенів (усі, що походять від початкового входу). Обидва мусять увійти заново, а зловмисник втрачає доступ.
Для цього сервер зберігає ланцюжок: кожен refresh-токен знає свою «сім'ю» і чи був використаний.
Практичні деталі:
- паралельні запити: дві вкладки одночасно оновлюють токен з тим самим refresh-токеном - друга виглядає як «повторне використання». Рішення - короткий період допуску для того самого токена або серіалізація оновлення на клієнті;
- абсолютний термін життя сесії: навіть з ротацією ланцюжок не повинен жити вічно (наприклад, 30-90 днів до повторного входу);
- відкликання при зміні пароля і виході - усіх refresh-токенів користувача;
- зберігання: хеш у базі (як паролі); на клієнті - захищене сховище ОС для мобільних,
HttpOnly-cookie для вебу.
Для браузерних SPA взагалі варто зважити, чи потрібні токени: сесійна cookie з бекендом на тому ж домені (Sanctum SPA) чи патерн BFF (бекенд для фронтенду, що тримає токени на сервері) прибирають токени з браузера.
У Laravel Passport refresh-токени є з коробки; терміни задаються Passport::tokensExpireIn() і Passport::refreshTokensExpireIn().
Докладніше в документації: RFC 9700: найкращі практики безпеки OAuth 2.0