JavaRush /Курсы /Модуль 3: React /Типизация данных токена и интерфейсы пользователя

Типизация данных токена и интерфейсы пользователя

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

Зачем типизировать данные токена и интерфейсы пользователя?

Вот представьте: вы в вашем приложении получаете с сервера токен, и там внутри — JSON с полезными данными, например, идентификатор пользователя, его роли или имя. Если вы забыли про TypeScript, то эти данные превращаются в неструктурированную массу информации. С вами остаётся только одно — молиться, чтобы сервер отправил всё, что нужно, и в нужном формате (спойлер: не всегда так бывает).

Типизация данных токена и пользователя помогает:

  1. Убедиться, что данные внутри токена имеют ожидаемый формат.
  2. Упростить работу с токеном в коде: IDE будет подсказывать вам поля токена.
  3. Избежать ошибок при работе с данными пользователя.

А теперь перейдём к практике!

Структура JWT

Напомним, что JWT разделён на три части:

  1. Header (заголовок) — метаданные токена.
  2. Payload (полезная нагрузка) — сюда обычно входят данные о пользователе.
  3. Signature (подпись) — проверяет подлинность токена.

В основном мы будем работать с полезной нагрузкой payload, так как именно там хранится информация о пользователе. Вот пример содержимого payload в JWT:

{
  "sub": "1234567890",
  "name": "John Doe",
  "role": "admin",
  "iat": 1516239022,
  "exp": 1516242622
}

Какие данные могут быть в токене?

  • sub (subject) — идентификатор пользователя.
  • name — имя пользователя.
  • role — роль пользователя в системе (например, администратор или обычный пользователь).
  • iat (issued at) — время создания токена.
  • exp (expiration) — время истечения токена.

Давайте опишем это с TypeScript!

Типизация токена

Создадим интерфейс JwtPayload для данных токена:

// Интерфейс для полезной нагрузки токена
export interface JwtPayload {
  sub: string; // Идентификатор пользователя (обычно строка)
  name: string; // Имя пользователя
  role: 'admin' | 'user'; // Роль пользователя (можем задать конкретные значения)
  iat?: number; // Время создания токена (опционально)
  exp?: number; // Время истечения токена (опционально)
}

Обратите внимание на два момента:

  1. Поля iat и exp сделали опциональными ?, потому что сервер может их не вернуть, но это не должно ломать ваш код.
  2. Тип для role ограничили строковыми литералами 'admin' | 'user' — это защищает нас от случайных ошибок и гарантирует, что роль всегда имеет одно из указанных значений.

Теперь, когда вы получите JWT из запроса, вы можете его декодировать (например, с помощью библиотеки jsonwebtoken) и точно знать, какие данные вас ожидают.

Типизация пользователя

Данные пользователя в вашем приложении чаще всего идут отдельно от токена. Например, после успешного входа в систему вы можете получать полный объект пользователя с дополнительной информацией, такой как email, аватар или статус.

Создадим интерфейс User для пользователя:

export interface User {
  id: string; // Уникальный идентификатор пользователя
  name: string; // Имя пользователя
  email: string; // Email пользователя
  role: 'admin' | 'user'; // Роль пользователя (совпадает с данными в токене)
  avatarUrl?: string; // Ссылка на аватар (опционально)
}

Связь токена и пользователя

Теперь представьте, что мы получаем токен с минимальным набором данных, а при необходимости делаем запрос на сервер за более полными данными о пользователе. Например:

  • В токене содержатся только sub, name и role, чтобы снизить его размер и не передавать лишние данные.
  • Полный объект пользователя мы храним в состоянии приложения (например, в Redux или Context API).

Пример работы с токеном

Окей, давайте напишем функцию для декодирования токена и преобразования его в данные пользователя:

import jwtDecode from 'jwt-decode'; // Библиотека для декодирования JWT
import { JwtPayload, User } from './types'; // Наши интерфейсы

// Функция для получения данных пользователя из токена
export function getUserFromToken(token: string): User {
  // Декодируем токен с помощью jwt-decode
  const payload = jwtDecode<JwtPayload>(token);

  // Преобразуем данные из токена в объект User
  const user: User = {
    id: payload.sub,
    name: payload.name,
    email: '', // Email может быть недоступен в токене
    role: payload.role,
    avatarUrl: undefined, // Например, аватар может быть загружен позже
  };

  return user;
}

Что здесь происходит:

  1. Мы декодируем токен с помощью jwt-decode (не забудьте установить библиотеку: npm install jwt-decode).
  2. Сопоставляем данные из токена с интерфейсом User.
  3. Возвращаем готовый объект.

Типизация состояния пользователя

В React-приложениях аутентификация часто связывается с состоянием. Обычно мы храним объект текущего пользователя в глобальном состоянии или контексте.

Создадим интерфейс для состояния аутентификации:

export interface AuthState {
  user: User | null; // Текущий пользователь (если вошёл в систему)
  token: string | null; // JWT-токен
  isAuthenticated: boolean; // Флаг аутентификации
}

Глобальное состояние или контекст

Допустим, мы используем Context API для управления этим состоянием:

import React, { createContext, useState, useContext } from 'react';
import { AuthState } from './types';

// Начальное состояние аутентификации
const initialAuthState: AuthState = {
  user: null,
  token: null,
  isAuthenticated: false,
};

// Создаём контекст
const AuthContext = createContext<{
  authState: AuthState;
  setAuthState: React.Dispatch<React.SetStateAction<AuthState>>;
}>({
  authState: initialAuthState,
  setAuthState: () => {},
});

// Хук для доступа к состоянию аутентификации
export const useAuth = () => useContext(AuthContext);

// Провайдер для AuthContext
export const AuthProvider: React.FC = ({ children }) => {
  const [authState, setAuthState] = useState(initialAuthState);

  return (
    <AuthContext.Provider value={{ authState, setAuthState }}>
      {children}
    </AuthContext.Provider>
  );
};

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

Обработка данных пользователя в реальном приложении

На практике вы можете:

  1. После успешного входа в систему сохранить токен в localStorage или sessionStorage.
  2. С помощью токена получить дополнительные данные пользователя с сервера.
  3. Сохранять всё это в глобальном состоянии и использовать во всём приложении.

Например, в компоненте входа:

import { useAuth } from './AuthProvider';

const Login = () => {
  const { setAuthState } = useAuth();

  const handleLogin = async () => {
    const token = await loginUserOnServer(); // Ваш API-запрос
    const user = getUserFromToken(token);

    setAuthState({
      user,
      token,
      isAuthenticated: true,
    });

    localStorage.setItem('token', token); // Сохраняем токен
  };

  return <button onClick={handleLogin}>Войти</button>;
};

Типичные ошибки

Одной из частых ошибок является отсутствие проверки структуры токена. Всегда проверяйте, что поле действительно существует в объекте, перед использованием его значения. Также не забывайте, что токен может быть просрочен!

if (!payload.sub) {
  throw new Error('Недействительный токен!');
}

Теперь вы знаете, как типизировать данные токена и пользователя, а также как это всё связать воедино. Надеюсь, вам понравилось!

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