Я учился создавать систему входа в систему с использованием токенов JWT. И как мы знаем, эта стратегия предполагает создание двух токенов: access_token и refresh_token.
Что касается refresh_token, то он сохраняется в файле cookie и управляется на стороне сервера. access_token управляется пользователем (внешнее приложение), и большинство руководств в Интернете сохраняют его в localStorage.
После некоторых исследований я пришел к выводу, что лучшая альтернатива — сохранить этот токен (access_token) в памяти приложения.
Чтобы попытаться достичь того же результата, я создал контекст под названием AuthContext.js:
import { useState, createContext, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import axios from 'axios';
export const AuthContext = createContext({});
const AuthProvider = ({ children }) => {
const [accessToken, setAccessToken] = useState(null);
const signIn = async (email, password) => {
setLoadingAuth(true);
try {
const { data } = await axios.post('http://localhost/signIn', { email: email, password: password });
setAccessToken(data.accessToken);
setLoadingAuth(false);
navigate('/dashboard');
} catch (error) {
alert(error.response.data.message);
setLoadingAuth(false);
console.error(error);
}
}
return(
<AuthContext.Provider value = {{ accessToken, userInfo, loadingAuth, loading, signIn, logout }}>
{children}
</AuthContext.Provider>
);
}
export default AuthProvider;
Который импортируется в App.js вместе с DOM-библиотекой React Router:
export default function App() {
return (
<BrowserRouter>
<AuthProvider>
<RouterApp />
</AuthProvider>
</BrowserRouter>
);
}
Проблема в том, что после того, как пользователь обновляет экран браузера, access_token, хранившийся в состоянии accessToken, ПОТЕРЯЕТСЯ.
Очевидно, потому что оно сохраняется в памяти. Однако, поскольку мое приложение не закрылось, я считаю, что accessToken все равно должен хранить это значение, верно?
Чтобы обойти эту проблему, я подумываю о создании SINGLETON, то есть класса, который будет использовать функцию object.freeze, чтобы сохранять accessToken в течение некоторого времени.
Будет ли это лучшей альтернативой?



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


Вы можете использовать локальное хранилище
// save the access_token
localstorage.setItem("access_token", "access_token value")
// read the access_token
localstorage.getItem("access_token")плохая практика безопасности
Чтобы сохранить токен доступа в памяти приложения, вы можете создать класс для его управления, например:
аутентификацияManager.js
let accessToken = "";
export default class AuthenticationManager {
setAccessToken(s) {
accessToken = s;
}
getAccessToken() {
return accessToken;
}
}
let authenticationManager = new AuthenticationManager();
export const getAuthenticationManager = () => {
return authenticationManager;
};
И затем после успешного входа в систему вы можете установить токен доступа. Что-то подобное:
аутентификацияService.js
import { getAuthenticationManager } from "./authenticationManager";
...
export async function login(loginUserRequest) {
const response = await authenticationApi.login(loginUserRequest);
if (response.ok) {
const userTokenResponse = await response.json();
// Setting access token
getAuthenticationManager().setAccessToken(userTokenResponse.access_token);
}
}
...
Если у вас есть класс, выполняющий запросы API, вы можете проверить, установлен ли токен доступа, прежде чем выполнять запрос. Если это не так, вы можете использовать токен обновления, чтобы получить новый токен доступа перед выполнением исходного запроса. Что-то подобное:
apiClient.js
import { getAuthenticationManager } from "./authenticationManager";
const SERVER_ADDRESS = "https://server.com";
const MAX_REFRESH_ACCESS_TOKEN_ATTEMPTS = 2;
export default class ApiClient {
constructor() {
this.getAccessToken = () =>
getAuthenticationManager()?.getAccessToken() || "";
this.setAccessToken = (accessToken) =>
getAuthenticationManager()?.setAccessToken(accessToken);
}
/**
* Refreshes the access token for the user.
* If the refresh is successful, updates the access token.
*/
async refreshAccessToken() {
const response = await fetch(`${SERVER_ADDRESS}/api/refresh_access_token`, {
method: "GET",
credentials: "include",
});
if (response.ok) {
const responseBody = await response.json();
this.setAccessToken(responseBody.access_token);
}
}
/**
* Sends a REST request to the server.
* If the access token is not available or has expired, it will be refreshed before sending the request.
* If the response status is 401 (Unauthorized), it will attempt to refresh the access token and retry the request.
* @param uri - The URI of the request.
* @param options - The options for the request.
* @param attempts - The number of attempts to refresh the access token if needed. Defaults to MAX_REFRESH_ACCESS_TOKEN_ATTEMPTS.
* @returns A Promise that resolves to the response of the request.
*/
async restRequest(
uri,
options,
attempts = MAX_REFRESH_ACCESS_TOKEN_ATTEMPTS
) {
if (
this.getAccessToken() === "" &&
attempts === MAX_REFRESH_ACCESS_TOKEN_ATTEMPTS
) {
await this.refreshAccessToken();
}
let response = await fetch(SERVER_ADDRESS + uri, {
...options,
credentials: "include",
headers: {
...options.headers,
Authorization: "Bearer " + this.getAccessToken(),
},
});
/**
* If the response status is 401 (Unauthorized), an attempt will be made
* to refresh the access token and retry the request. The number of attempts
* to refresh the access token is decremented by 1.
*/
if (response.status === 401 && attempts > 0) {
await this.refreshAccessToken();
response = await this.restRequest(uri, options, attempts - 1);
}
return response;
}
}
Пока ваш файл cookie токена обновления настроен на сохранение между сеансами браузера, его можно использовать для обновления токена доступа каждый раз, когда создается новый сеанс (или обновляется браузер).
Если срок действия токена доступа, хранящегося в памяти приложения, истек, он будет обновлен при использовании для запроса API.
«лучшая альтернатива» чему? Какую проблему вы пытаетесь решить с помощью своей альтернативы? Синглтон также будет потерян после обновления страницы.