JavaRush /Курсы /Модуль 3: React /Автоматическое обновление токена и проверка сессии пользо...

Автоматическое обновление токена и проверка сессии пользователя

Модуль 3: React
15 уровень , 8 лекция
Открыта

Зачем обновлять токен?

JWT, как правило, имеет срок действия (expiry). Это обусловлено соображениями безопасности, чтобы украденные токены не могли использоваться бесконечно. Однако, если срок действия токена истекает слишком быстро, пользователь будет испытывать неудобства. Чтобы решить эту проблему:

  1. Мы создаём Refresh Token, который используется для выпуска нового Access Token.
  2. Refresh Token имеет больший срок действия и обновляется реже.

Наша задача — автоматически проверять, не истекает ли токен, и обновлять его, если это необходимо.

Обновление токенов в приложении

Шаг 1. Настройка сервера для обработки Refresh Token

Сначала добавим поддержку Refresh Token на сервере. Вот пример на стороне Node.js:

import jwt from 'jsonwebtoken';
import express from 'express';

const app = express();
const ACCESS_TOKEN_SECRET = 'yourAccessTokenSecret';
const REFRESH_TOKEN_SECRET = 'yourRefreshTokenSecret';

let refreshTokens: string[] = [];

// Генерация токенов
function generateAccessToken(user: { id: string }) {
  return jwt.sign(user, ACCESS_TOKEN_SECRET, { expiresIn: '15m' });
}

function generateRefreshToken(user: { id: string }) {
  const refreshToken = jwt.sign(user, REFRESH_TOKEN_SECRET);
  refreshTokens.push(refreshToken);
  return refreshToken;
}

// Эндпоинт для обновления токена
app.post('/token', (req, res) => {
  const { token } = req.body;
  if (!token || !refreshTokens.includes(token)) {
    return res.sendStatus(403);
  }

  jwt.verify(token, REFRESH_TOKEN_SECRET, (err, user) => {
    if (err) return res.sendStatus(403);
    const newAccessToken = generateAccessToken({ id: user.id });
    res.json({ accessToken: newAccessToken });
  });
});

// Пример маршрута авторизации
app.post('/login', (req, res) => {
  // В реальном мире здесь идет проверка пользователя
  const user = { id: '123' }; // Пример данных пользователя
  const accessToken = generateAccessToken(user);
  const refreshToken = generateRefreshToken(user);
  res.json({ accessToken, refreshToken });
});

app.listen(4000, () => {
  console.log('Auth server running on port 4000');
});

Теперь сервер умеет выдавать новые Access Token при запросе с действительным Refresh Token.

Шаг 2. Настройка обновления токена на клиенте

Создадим метод для запроса нового Access Token в нашем AuthService:

export const refreshAccessToken = async (refreshToken: string): Promise<string | null> => {
  try {
    const response = await fetch('http://localhost:4000/token', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({ token: refreshToken }),
    });

    if (!response.ok) {
      throw new Error('Failed to refresh token');
    }

    const data = await response.json();
    return data.accessToken;
  } catch (error) {
    console.error('Error refreshing token:', error);
    return null;
  }
};

Шаг 3. Проверка срока действия токена

Добавим проверку истечения срока действия токена. Для этого раскодируем JWT, чтобы узнать его время exp:

import jwt_decode from 'jwt-decode';

interface JwtPayload {
  exp: number;
}

export const isTokenExpiringSoon = (token: string): boolean => {
  const decoded = jwt_decode<JwtPayload>(token);
  const currentTime = Math.floor(Date.now() / 1000); // Текущее время в секундах
  const timeLeft = decoded.exp - currentTime;
  return timeLeft < 60; // Считаем, что истечёт через 1 минуту
};

Шаг 4. Автоматическое обновление токена

Теперь автоматизируем процесс обновления. Создадим кастомный хук useAuthToken:

import { useState, useEffect } from 'react';

export const useAuthToken = (initialToken: string | null) => {
  const [accessToken, setAccessToken] = useState<string | null>(initialToken);

  useEffect(() => {
    const interval = setInterval(async () => {
      if (accessToken && isTokenExpiringSoon(accessToken)) {
        const refreshToken = localStorage.getItem('refreshToken');
        if (refreshToken) {
          const newToken = await refreshAccessToken(refreshToken);
          if (newToken) {
            setAccessToken(newToken);
            localStorage.setItem('accessToken', newToken);
          } else {
            console.error('Failed to refresh token');
          }
        }
      }
    }, 5000); // Проверяем каждые 5 секунд

    return () => clearInterval(interval);
  }, [accessToken]);

  return [accessToken, setAccessToken] as const;
};

Теперь токен автоматически обновляется, если близок к истечению.

Проверка активности сессии

Также важно проверять, активна ли сессия пользователя. Например, при загрузке приложения вы можете анализировать, есть ли у пользователя действительный токен.

Шаг 5. Проверка сессии при запуске приложения

Напишем функцию для проверки валидности токена и подключения к хранилищу Refresh Token:

export const checkSession = async (): Promise<boolean> => {
  const accessToken = localStorage.getItem('accessToken');
  const refreshToken = localStorage.getItem('refreshToken');

  if (!accessToken || !refreshToken) {
    return false;
  }

  if (isTokenExpiringSoon(accessToken)) {
    const newToken = await refreshAccessToken(refreshToken);
    if (newToken) {
      localStorage.setItem('accessToken', newToken);
      return true;
    }
    return false;
  }

  return true;
};

Теперь мы вызываем эту функцию при запуске приложения:

useEffect(() => {
  const initializeSession = async () => {
    const sessionValid = await checkSession();
    if (!sessionValid) {
      console.error('Session expired');
      // Перенаправляем на страницу логина
    }
  };

  initializeSession();
}, []);

Типичные проблемы и их решение

  1. Токены случайно удалились из хранилища. Если вы теряете accessToken и refreshToken, пользователь будет вынужден снова логиниться. Всегда проверяйте синхронизацию токенов.

  2. Некорректно настроен сервер. Убедитесь, что сервер правильно обрабатывает запросы обновления и возвращает новый токен с актуальной подписью.

  3. Частые запросы на обновление. Настройте разумный интервал проверки setInterval, чтобы не перегружать сервер.

Вы сделали это! Теперь ваши приложения могут умело управлять сессиями, используя автоматическое обновление токенов. Настало время переходить к защите маршрутов и интеграции с React Router.

2
Задача
Модуль 3: React, 15 уровень, 8 лекция
Недоступна
Раскодирование и проверка срока действия JWT
Раскодирование и проверка срока действия JWT
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ