1️⃣Auth / JWT
Приложение загружается. Me запрос отрабатывает, с колодами реализованы CRUD операции. Но сейчас мы имеет такое поведение кода связанного с заглушкой в headers
Удалим заглушку из flashcardsApi.ts
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
export const flashcardsApi = createApi({
baseQuery: fetchBaseQuery({
baseUrl: 'https://api.flashcards.andrii.es',
}),
endpoints: () => ({}),
reducerPath: 'flashcardsApi',
tagTypes: ['Decks', 'Me'],
})
❌Соответственно видим что me запрос падает с ошибкой
Логинимся с тестовыми данными (test@test.com / test) и видим, что в респонсе нам возвращаются токены

✅ jwt
Поговорим теперь про JWT токены
JWT токен состоит из 3-х частей:
HEADER Заголовок обычно состоит из двух частей: типа токена, который является JWT, и используемого алгоритма подписи, например HMAC SHA256 или RSA.
PAYLOAD - данные о юзере Полезная нагрузка содержит утверждения. Утверждения - это утверждения о сущности (обычно, пользователе) и дополнительных данных.
VERIFY SIGNATURE (подпись). Чтобы создать подпись, берется закодированный заголовок, закодированная полезная нагрузка, секретный ключ и алгоритм, указанный в заголовке. Например, если используется HMAC SHA256, подпись создается следующим образом ✳️секретный ключ генерируется на бекенде ❗Подпись используется для проверки подлинности отправителя сообщения и целостности сообщения.

Давайте теперь попрактикуемся и сохраним токен в
localstorage
Для того чтобы реализовать данную задачу нам понадобится onQueryStarted
❗Ошибка в коде. Не указан response login endpoint
❌ удалите invalidatesTags: ['Me'], т.к. после логин запроса не надо делать me
После данного шага данные лягут в localstorage 🚀
Данные сохранились в
localstorage.И что с того ? Нас не перекидывает на страницу колод. Нам нужно к каждому запросу цеплять access token
Существуют различные схемы Аутентификации
❗Мы будем использовать Bearer
✳️prepareHeaders отрабатывает после каждого запроса, т.е. к каждому запросу будет цепляться Bearer token

После данного шага после логинизации мы должны оказаться на странице колод 🚀
✅ Безопасность
Хранение JWT (JSON Web Token) в localStorage имеет свои риски и преимущества. Вот основные моменты, которые нужно учитывать:
Преимущества:
Простота использования:
localStorageлегко использовать и доступ к нему можно получить из любого места в вашем JavaScript коде.Долгосрочное хранение: Данные в
localStorageсохраняются даже после закрытия вкладки или перезагрузки браузера, что делает его удобным для хранения токенов, которые не истекают быстро.
Риски:
Уязвимость к XSS (Cross-Site Scripting): Если ваш сайт подвержен атакам XSS, злоумышленник может получить доступ к
localStorageи украсть токен. XSS является одной из самых распространенных уязвимостей веб-приложений.Нет встроенной защиты: В отличие от HTTP-only cookies, которые не могут быть прочитаны JavaScript-ом,
localStorageдоступен из любого скрипта на странице.
Альтернативы:
HTTP-only cookies: Хранение JWT в HTTP-only cookies уменьшает риск XSS, так как JavaScript не может получить доступ к таким cookies. Однако это делает вас уязвимым к атакам CSRF (Cross-Site Request Forgery), для защиты от которых нужно внедрять дополнительные меры (например, токены CSRF).
Рекомендации:
Используйте HTTPS: Всегда используйте HTTPS для защиты данных в транзите.
Проверяйте уязвимости XSS: Убедитесь, что ваш сайт не подвержен атакам XSS. Используйте Content Security Policy (CSP) для ограничения того, какие скрипты могут выполняться на вашей странице.
Срок действия токена: Устанавливайте краткосрочный срок действия для токенов и используйте механизмы обновления (refresh tokens).
Сегментируйте доступ: Ограничивайте область применения токенов до минимально необходимого уровня доступа.
Итог:
Хранение JWT в localStorage может быть приемлемым решением, если ваш сайт защищен от XSS и вы принимаете соответствующие меры предосторожности. Однако для лучшей безопасности рассмотрите использование HTTP-only cookies, особенно если у вас есть опыт защиты от CSRF атак.
❗ Прекращение поддержки сторонних файлов cookie
✳️ Если говорить простыми словами, то
плохо делать токен маложивущим, т.к. постоянно будет выкидывать пользователя
плохо делать токен долгоживущим, т.к. если токен украдут то им могут пользоваться условно неделю
Поэтому у нас 2 токена: access и refresh токены
access токен (токен доступа) как правило маложивущий 5-15 минут
refresh токен (токен обновления access токена)
access токен перехватить легко, т.к. он идет на каждый запрос, а refresh токен сложно, т.к. он 1 раз в 15 минут (когда тухнет access токен)
✅ Теория - работа с access / refresh токенами
Последовательность следующая:
идет первый запрос, возвращаются данные и access и refresh токены
проходит 15 минут / access протухает
вам приходит 401 с бекенда
вы автоматом перехватывает результат 401 запроса
делаете запрос на специальный endpoint с refresh токеном
и в ответ приходит новые access и refresh токены
вы берете новый access и сетаете его в
localstorageи повторяете тот запрос который упал
и естественно этот запрос пройдет 🚀
Итого: пользователь понятия не имеет, что у него какой-то запрос упал, потом еще раз пошел и со 2-го раза прошел. При этом пользователь в безопасности ценной лишь повторного запроса (refresh запрос всегда очень быстрый и простой)
И безопасно и пользователю нормально 🚀
✅Практика - работа с access / refresh токенами
Будем использовать вариант с async-mutex для предотвращения многократных вызовов '/refreshToken', когда несколько вызовов завершаются с ошибкой 401 Unauthorized.
Создайте файл
flashCardsBaseQuery.tsи скопируйте кодвынесите
fetchBaseQuery из flashcardsApi.tsвflashCardsBaseQuery.ts
Теперь давайте разбираться и подстраивать под свой код
flashCardsBaseQuery.ts
baseQueryWithReauth вызывается на каждый запрос. Проверим это и посмотрим что нам на данные которые к нам приходят

Т.е. args - это аргументы, которые приходят в запрос
Следующая строка - это результат выполнения запроса

Всю эту движуху мы затеяли чтобы отлавливать 401 ошибку. Поэтому вылогинимся (зачистим localstorage) и посмотрим на результат

Следующая строка которую нужно подкорректировать
Для того, чтобы ее правильно написать посмотри на refresh token endpoint
Прописываем аргументы согласно ендпоинту
Добавляем в header запроса refresh token, который достаем из localstorage
Чтобы продемонстировать refreshResult нужно залогиниться, но не нажимать remember me
❗При rememberMe: false, acess токен протухает через 10 секунд. Сделано в целях тестирования
❗Необходимо добавить условие в fetchBaseQuery

Двигаемся дальше. Теперь нам необходимо положить в localstotage новые токены и повторить запрос
С типизацией придется мучаться т.к. библиотека RTK query не возвращает нам
QueryReturnValueчтобы сделать редирект на логин экспортируйте роутер
Когда будете делать logout, зачищаейте localstorage
И после этого все должно работать 🚀
Last updated