📜 ⬆️ ⬇️

Token, refresh token and creating an asynchronous wrapper for the REST request

image In this tutorial, we briefly analyze how REST requests to API are implemented, requiring the user to be authorized, and create an asynchronous “wrapper” for the request, which will check the authorization and update it in a timely manner.

Authorization data


Having made a REST request to api, where we sent the login and password, in response we get json of the following format (values ​​are random and the strings are usually longer):

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

There may be more fields in the response, for example, “token_type” , “expires_on” , etc., but for this implementation, we need only the three fields listed above.
Let's take a closer look at them:
')

Getting a token


Now create a function that will receive the json described above and save it.

We will store the data for authorization in sessionStorage or localStorage , depending on our needs. In the first case, the data is stored until the user terminates the session or closes the browser, in the second case, the data in the browser will be stored indefinitely, until for some reason the localStorage is cleared.

The function to save the token in sessionStorage:


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

Function to get a token:


 function getTokenData(login, password) { return fetch('api/auth', { method: 'POST', credentials: 'include', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json', }, 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(); }); } 

Thus, we got a token with the “access_token” , “refresh_token” and “expires_in” fields and saved it in the sessionStorage for further use.

Token Update


The token we received earlier has a limited lifetime, which is specified in the "expires_in" field. After his lifetime expires, the user will not be able to get new data by sending this token in the request, so you need to get a new token.

We can get the token in two ways: the first way is to reauthorize by sending a username and password to the server. But this does not suit us, because to force the user to re-enter the authorization data every time after a certain period of time has passed is wrong, it must be done automatically. But it is not safe to store the login / password pair for automatic sending somewhere in the memory, this is exactly what the “refresh_token” is needed for , which was received earlier with the “access_token” and is stored in sessionStorage. By sending this token to another address, which is provided by api, we will be able to receive in response a new “fresh” token.

Function to update the token


 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(); }); } 

Using the code above, we overwritten the token in sessionStorage and now we can send requests to the api using the new one.

Creating a wrapper function


Now we will create a function that will add the data for authorization to the request header, and, if necessary, update them automatically before making the request.

Since if the token has expired, we will need to request a new token, our function will be asynchronous. For this we will use the async / await construction.

Wrapper function


 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 } 

Using the code above, we created a function that will add a token to requests in api. With this function we can replace fetch in the requests we need, where authorization is required and for this we do not need to change the syntax or add any more data to the arguments.
Just enough to “import” it into the file and replace it with a standard fetch.

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

Source: https://habr.com/ru/post/456188/


All Articles