Хабрахабр

[Из песочницы] Token, refresh token и создание асинхронной обертки для REST-запроса

imageВ данном туториале мы кратко разберем, как реализовываются REST-запросы к API, требующие, чтобы пользователь был авторизован, и создадим асинхронную «обертку» для запроса, которая будет проверять авторизацию и своевременно ее обновлять.

Данные для авторизации

Сделав REST-запрос к api, куда мы отправили логин и пароль, в ответ мы получаем json следующего формата (значения взяты рандомные и строки обычно длиннее):

{ "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJPbmxpbmUgSld", "refresh_token": "1eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJPbmxpbmUgS", "expires_in": 124234149563
}

Полей в ответе может быть больше, например еще «token_type», «expires_on» и т. д., но, для данной реализации, нам нужны только три поля, приведенные выше.
Давайте их рассмотрим подробнее:

  • access_token — токен, который нам нужно будет отправлять в шапке каждого запроса, для получения данных в ответ
  • refresh_token — токен, который нам нужно будет отправлять, для получения нового токена, когда истечет время жизни старого
  • expires_in — время жизни токена в секундах

Получение токена

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

В первом случае данные хранятся до тех пор, пока пользователь не завершит сеанс или не закроет браузер, во втором случае данные в браузере будут храниться неограниченное время, пока по каким-либо причинам localStorage не будет очищен. Хранить данные для авторизации мы будем в sessionStorage или localStorage, в зависимости от наших нужд.

Функция для сохранения токена в sessionStorage:

function saveToken(token) { sessionStorage.setItem('tokenData', JSON.stringify(token));
}

Функция для получения токена:


function getTokenData(login, password) , body: JSON.stringify({ login, password, }), }) .then((res) => { if (res.status === 200) { const tokenData = res.json(); saveToken(JSON.stringify(tokenData)); // сохраняем полученный токен в sessionStorage, с помощью функции, заданной ранее return Promise.resolve() } return Promise.reject(); });
}

Таким образом мы получили токен с полями «access_token», «refresh_token» и «expires_in» и сохранили его в sessionStorage для дальнейшего использования.

Обновление токена

Токен полученный нами ранее имеет ограниченное время жизни, которое задано в поле «expires_in». После того как его время жизни истечет, пользователь не сможет получить новые данные, отправляя данный токен в запросе, поэтому нужно получить новый токен.

Но это нам не подходит, т. Получить токен мы можем двумя способами: первый способ это заново авторизовавшись, отправив логин и пароль на сервер. заставлять пользователя каждый раз заново вводить данные авторизации по истечению какого-то отрезка времени — неправильно, это надо делать автоматически. к. Отправив данный токен на другой адрес, который предоставляет api, мы сможем получить в ответ новый «свежий» токен. Но хранить где-то в памяти пару логин/пароль для автоматической отправки небезопасно, именно для этого и нужен «refresh_token», который был получен ранее вместе с «access_token» и хранится в sessionStorage.

Функция для обновления токена


function refreshToken(token) { return fetch('api/auth/refreshToken', { method: 'POST', credentials: 'include', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json', }, body: JSON.stringify({ token, }), }) .then((res) => { if (res.status === 200) { const tokenData = res.json(); saveToken(JSON.stringify(tokenData)); // сохраняем полученный обновленный токен в sessionStorage, с помощью функции, заданной ранее return Promise.resolve(); } return Promise.reject(); });
}

С помощью кода выше мы перезаписали токен в sessionStorage и теперь по новой можем отправлять запросы к api.

Создание функции-обертки

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

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

Функция-обертка


export async function fetchWithAuth(url, options) { const loginUrl = '/login'; // url страницы для авторизации let tokenData = null; // объявляем локальную переменную tokenData if (sessionStorage.authToken) { // если в sessionStorage присутствует tokenData, то берем её tokenData = JSON.parse(localStorage.tokenData); } else { return window.location.replace(loginUrl); // если токен отсутствует, то перенаправляем пользователя на страницу авторизации } if (!options.headers) { // если в запросе отсутствует headers, то задаем их options.headers = {}; } if (tokenData) { if (Date.now() >= tokenData.expires_on * 1000) { // проверяем не истек ли срок жизни токена try { const newToken = await refreshToken(tokenData.refresh_token); // если истек, то обновляем токен с помощью refresh_token saveToken(newToken); } catch () { // если тут что-то пошло не так, то перенаправляем пользователя на страницу авторизации return window.location.replace(loginUrl); } } options.headers.Authorization = `Bearer ${tokenData.token}`; // добавляем токен в headers запроса } return fetch(url, options); // возвращаем изначальную функцию, но уже с валидным токеном в headers
}

С помощью кода выше мы создали функцию, которая будет добавлять токен к запросам в api. На эту функцию мы можем заменить fetch в нужных нам запросах, где требуется авторизация и для этого нам не потребуется менять синтаксис или добавлять в аргументы еще какие-либо данные.
Просто достаточно будет «импортнуть» ее в файл и заменить на нее стандартный fetch.

import fetchWithAuth from './api'; function getData() { return fetchWithAuth('api/data', options)
}

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть