Как сохранить access_token в приложении ReactJS?

Я учился создавать систему входа в систему с использованием токенов 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 в течение некоторого времени.

Будет ли это лучшей альтернативой?

«лучшая альтернатива» чему? Какую проблему вы пытаетесь решить с помощью своей альтернативы? Синглтон также будет потерян после обновления страницы.

jabaa 23.06.2024 22:24
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
1
1
58
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Вы можете использовать локальное хранилище

// save the access_token

localstorage.setItem("access_token", "access_token value")

// read the access_token

localstorage.getItem("access_token")

плохая практика безопасности

Kyle Xyian Dilbeck 25.06.2024 01:03
Ответ принят как подходящий

Чтобы сохранить токен доступа в памяти приложения, вы можете создать класс для его управления, например:

аутентификация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.

Другие вопросы по теме