Если вы ленивы, как я, и не хотите читать всю статью, вот GitHub gist, содержащий шпаргалку для обновляемого токена с использованием перехватчиков Axios - https://gist.github.com/alitoshmatov-novalab/9ba256a7981da4f397072f0679f4344e#file- axios-js
Когда я был новичком в этой области, я понятия не имел, как аутентификация работает на стороне клиента, и мне потребовалось много времени, чтобы понять концепцию аутентификации. Итак, я здесь, чтобы сделать все возможное, чтобы облегчить вам переход к концепции аутентификации на основе токенов на стороне клиента.
Как это работает?
Давайте освежим наши знания о том, как работает аутентификация на основе токенов. Таким образом, в основном, когда клиент запрашивает вход с действительными учетными данными, внутренний сервер отвечает уникальным токеном доступа, который хранит некоторые данные, связанные с пользователем и сеансом, в зашифрованном формате. После успешной аутентификации каждый раз, когда клиент делает запрос, этот токен также отправляется с данными запроса, поэтому внутренний сервер может проверить и определить, какой пользователь делает запрос, и / или проверить, есть ли у него разрешение на это.
В общем, мы можем сказать, что этот токен действует как ключ доступа, и внутренний сервер не откроет двери для неправильных ключей.
Однако токен доступа недолговечен, что означает, что он будет действителен в течение нескольких минут или, реже, часов. По истечении указанного времени внутренний сервер не примет токен, и срок действия токена истечет. Что ж, вы могли бы сказать, что у есть пользователь для повторной аутентификации каждый раз, когда истекает срок его токена !? К счастью, в этом нет необходимости. Когда пользователь входит в систему, вместе с внутренним сервером токена доступа также отправляется токен обновления. Маркер обновления, как следует из названия, используется для обновления обычного токена по истечении срока его действия. Будет конечная точка API, которая принимает токен обновления в данных запроса и отвечает новым токеном доступа. Поэтому, когда клиентская сторона делает запрос и если внутренний сервер отвечает сообщением о том, что токен истек, клиентская сторона должна автоматически сделать запрос на новый токен доступа, а после получения нового токена доступа она также должна сделать исходный запрос еще раз. Вы можете проверить этот поток stackoverflow, чтобы подробнее изучить цель этого цикла, поскольку его объяснение заслуживает отдельной статьи.
Инициализация Axios
Если вы читаете эту статью, я предполагаю, что вы знаете, как делать базовые запросы с помощью Axios, также не стесняйтесь проверять официальную документацию.
axios.post('https://example.com/api/user', { firstName: 'Fred', lastName: 'Flintstone' }) .then(function (response) { console.log(response); }) .catch(function (error) { console.log(error); });
В основном это так и делается. Но мы, вероятно, выиграем, если инициализируем экземпляр Axios с предпочтительными конфигурациями.
const customRequest = axios.create({ baseURL: 'https://example.com/api/', headers: {'Request-Origin': 'website'} });
Как вы можете видеть, мы инициализировали наш экземпляр Axios с базовым URL-адресом и заголовком, теперь каждый запрос, сделанный с использованием экземпляра request
, по умолчанию будет иметь этот базовый URL-адрес и заголовок. Имейте в виду, что мы можем легко изменить эту конфигурацию.
customRequest.post('/user', { firstName: 'Fred', lastName: 'Flintstone' }) .then(function (response) { console.log(response); }) .catch(function (error) { console.log(error); });
Обработка входа в систему
Прежде чем переходить к обработке токенов, давайте займемся процессом входа в систему. Этот процесс очень прост. мы должны просто сделать запрос на вход в API, получить все необходимые данные и где-то их сохранить, возможно, в локальном хранилище или хранилище сеансов, чтобы сохранять данные между сеансами.
const handleLogin = (email, password)=>{ customRequest.post("/login", {email: email, password:password}) .then(response=>{ const token = response.data.token; const refreshToken = response.data.refreshToken; localStorage.setItem("accessToken", token); localStorage.setItem("refreshToken", refreshToken); const user = response.data.user; //handle user }) .catch(e=>console.log(e) }
Как вы можете видеть выше, мы получаем данные пользователя и оба токена с внутреннего сервера и сохраняем токены в локальном хранилище, чтобы сохранять их между сеансами. Исходя из ваших потребностей, вы можете обрабатывать пользовательские данные, как хотите.
Перехватчики
Теперь у нас есть экземпляр Axios и мы получили токены с сервера, давайте посмотрим, как мы можем отправлять токен авторизации в заголовке каждого нашего запроса. Обратите внимание, что мы можем получить токены только после того, как пользователь войдет в систему, и мы не можем с уверенностью сказать, что токен всегда сохраняется в локальном хранилище или где-то еще, а это означает, что наш токен не всегда будет доступен. Таким образом, мы не можем инициализировать наш экземпляр Axios с токеном в начале. Поэтому нам нужно использовать так называемые перехватчики.
Перехватчики - это своего рода промежуточное программное обеспечение, с помощью которого мы можем изменять запросы до их запуска и ответы до того, как они станут доступными на стороне клиента.
customRequest.interceptors.request.use(config=> { // Do something before request is sent return config; });
В приведенном выше коде мы применяем перехватчик к запросам, получая объект конфигурации, который содержит все данные, связанные с текущим запросом, который будет запущен. Не стесняйтесь исследовать этот объект, так как он содержит много интересных данных. Теперь мы можем изменить объект конфигурации и прикрепить дополнительный заголовок для переноса нашего токена.
customRequest.interceptors.request.use(config=> { const accessToken = localStorage.getItem("accessToken"); //checking if accessToken exists if(accessToken){ config.headers["Authorization"]=accessToken; } return config; });
Выше сначала мы извлекаем accessToken
из локального хранилища, если accessToken
существует в локальном хранилище, мы назначаем его заголовку с именем Авторизация и возвращаем измененный объект конфигурации, чтобы запрос мог продолжаться. Обратите внимание, что имя заголовка, которому вы назначаете значение токена, может отличаться в вашем проекте, уточните это с командой бэкэнд. Теперь мы закончили с отправкой токенов в заголовке каждого запроса, если токен существует.
Обновление токена
Итак, как должен работать обновляющий токен: в любой момент срок действия нашего токена доступа может истечь, поэтому, когда мы делаем запрос с истекшим токеном, внутренний сервер отвечает кодом состояния 401 (Неавторизованный). Получив этот ответ, мы пытаемся обновить токен доступа и повторить исходный запрос. Обратите внимание, что ответ для просроченного токена может отличаться, уточните у бэкэнд-команды. Применим перехватчик ответа:
customRequest.interceptors.response.use( (config)=> config);
Как и перехватчик запросов, перехватчик ответа также предоставляет нам объект конфигурации, который имеет все необходимые данные, включая ответ. Когда наш запрос будет успешным, мы просто хотим вернуть исходный объект конфигурации. Но когда мы получаем код состояния 401, наш ответ выдает ошибку. Перехватчики Axios принимают вторую функцию, которая является обратным вызовом для обработки ошибок.
customRequest.interceptors.response.use( (response) => response, async (error) => { //extracting response and config objects const { response, config } = error; //checking if error is Aunothorized error if (response.status === 401) { let refreshToken = localStorage.getItem("refreshToken"); if (refreshToken) { //if refresh token exists in local storage proceed try { //try refreshing token const data = await customRequest.post("/token/refresh/", { refresh: refreshToken, }); let accessToken = data.data.accessToken; if (accessToken) { //if request is successful and token exists in response //store it in local storage localStorage.setItem("token", accessToken); //with new token retry original request config.headers["Authorization"]=accessToken; return customRequest(config); } } catch (e) { console.log(e); } } } //if none above worked clear local storage and log user out logout(); return error; });
Эта часть может быть немного большой для понимания, давайте посмотрим по частям, что мы делаем:
- Прежде всего, мы извлекаем объект ответа, который содержит наш ответ с кодом ошибки, и объект конфигурации, который имеет все необходимые конфигурации, связанные с текущим запросом.
- Затем мы проверяем, является ли наш код ошибки 401, который является ответом на просроченные токены. Если это так, мы продолжаем, иначе он перейдет к концу функции и выйдет из системы.
- Теперь давайте извлечем наш токен обновления из локального хранилища и проверим, существует ли он на самом деле. Если у нас есть токен обновления, мы можем отправить запрос на новый токен доступа с токеном обновления.
- Если наш запрос на новый токен терпит неудачу, он переходит к концу функции и завершает цикл выходом пользователя из системы. После успешного получения нового токена доступа мы обновляем токен в локальном хранилище и повторяем наш исходный запрос с обновленным токеном.
- Обратите внимание, если какое-либо из наших условий if не выполняется или блок try-catch обнаруживает ошибку, мы переходим к концу функции и выходим пользователя из системы.
Вы можете добавить любую дополнительную логику или изменить этот код в зависимости от ваших потребностей, но в большинстве случаев последовательность операций остается прежней.
Я сделал небольшую шпаргалку для обработки токенов доступа и обновления с помощью перехватчиков Axios, посмотрите здесь. Также хотелось бы услышать предложения по любым модификациям и улучшениям. - https://gist.github.com/alitoshmatov-novalab/9ba256a7981da4f397072f0679f4344e#file-axios-js