← Назад к вопросам

Как работает авторизация через JWT?

2.2 Middle🔥 171 комментариев
#JavaScript Core

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI3 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Авторизация через JWT (JSON Web Token)

JWT - это стандартный способ передачи информации об аутентификации между сервером и клиентом. Это один из самых популярных методов авторизации в современных веб-приложениях.

Как работает JWT

JWT состоит из трех частей, разделенных точками:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

1. HEADER (заголовок)
2. PAYLOAD (данные)
3. SIGNATURE (подпись)

Каждая часть закодирована в Base64.

1. Структура JWT

Header (Заголовок)

// Расшифрованный header:
{
  "alg": "HS256",      // Алгоритм подписи
  "typ": "JWT"        // Тип токена
}

// Кодированный: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9

Payload (Данные)

// Расшифрованный payload:
{
  "sub": "user-123",           // Subject (ID пользователя)
  "name": "John Doe",          // Имя пользователя
  "email": "john@example.com", // Email
  "role": "admin",              // Роль
  "iat": 1516239022,            // Issued at (когда выдан)
  "exp": 1516325422             // Expiration (когда истекает)
}

Signature (Подпись)

// Подпись создается так:
SIGNATURE = HMACSHA256(
  base64UrlEncode(HEADER) + "." +
  base64UrlEncode(PAYLOAD),
  SECRET_KEY
)

2. Процесс авторизации

Этап 1: Логин

// Клиент отправляет учетные данные
const login = async (email, password) => {
  const response = await fetch('/api/auth/login', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ email, password })
  });
  
  const data = await response.json();
  // Сервер возвращает JWT токен
  return data.token; // eyJhbGc...
};

Этап 2: Сохранение токена

// Клиент сохраняет токен
const handleLogin = async (email, password) => {
  const token = await login(email, password);
  
  // Вариант 1: localStorage (не очень безопасно)
  localStorage.setItem('token', token);
  
  // Вариант 2: httpOnly cookie (безопаснее)
  // Сервер сам устанавливает cookie с флагом httpOnly
  // Браузер автоматически отправляет cookie
  
  // Вариант 3: sessionStorage (удаляется при закрытии)
  sessionStorage.setItem('token', token);
};

Этап 3: Отправка токена в запросах

// При каждом API запросе добавляем токен в header
const apiCall = async (url, options = {}) => {
  const token = localStorage.getItem('token');
  
  const response = await fetch(url, {
    ...options,
    headers: {
      ...options.headers,
      'Authorization': `Bearer ${token}`
    }
  });
  
  return response.json();
};

// Использование:
const userData = await apiCall('/api/users/me');

Этап 4: Проверка на сервере

// На сервере проверяют подпись и срок действия
const verifyJWT = (token, secretKey) => {
  try {
    // Декодируем и проверяем подпись
    const decoded = jwt.verify(token, secretKey);
    
    // Проверяем срок действия
    if (decoded.exp < Date.now() / 1000) {
      throw new Error('Token expired');
    }
    
    return decoded;
  } catch (error) {
    throw new Error('Invalid token');
  }
};

3. Практическая реализация на фронте

// auth.ts - утилиты для работы с JWT
import { jwtDecode } from 'jwt-decode';

interface DecodedToken {
  sub: string;
  email: string;
  role: string;
  exp: number;
}

const TOKEN_KEY = 'auth_token';

export const authService = {
  // Сохранить токен
  setToken: (token: string) => {
    localStorage.setItem(TOKEN_KEY, token);
  },
  
  // Получить токен
  getToken: (): string | null => {
    return localStorage.getItem(TOKEN_KEY);
  },
  
  // Удалить токен (выход)
  removeToken: () => {
    localStorage.removeItem(TOKEN_KEY);
  },
  
  // Проверить валидность
  isTokenValid: (): boolean => {
    const token = localStorage.getItem(TOKEN_KEY);
    if (!token) return false;
    
    try {
      const decoded: DecodedToken = jwtDecode(token);
      // Проверяем не истек ли токен
      return decoded.exp * 1000 > Date.now();
    } catch {
      return false;
    }
  },
  
  // Получить данные пользователя из токена
  getUser: (): DecodedToken | null => {
    const token = localStorage.getItem(TOKEN_KEY);
    if (!token) return null;
    
    try {
      return jwtDecode(token);
    } catch {
      return null;
    }
  }
};

4. React Hook для авторизации

interface AuthContextType {
  user: DecodedToken | null;
  isAuthenticated: boolean;
  login: (email: string, password: string) => Promise<void>;
  logout: () => void;
}

const AuthContext = createContext<AuthContextType | null>(null);

export function AuthProvider({ children }: { children: React.ReactNode }) {
  const [user, setUser] = useState<DecodedToken | null>(null);
  
  useEffect(() => {
    // При загрузке проверяем есть ли валидный токен
    if (authService.isTokenValid()) {
      setUser(authService.getUser());
    } else {
      authService.removeToken();
    }
  }, []);
  
  const login = async (email: string, password: string) => {
    const response = await fetch('/api/auth/login', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ email, password })
    });
    
    if (!response.ok) throw new Error('Login failed');
    
    const { token } = await response.json();
    authService.setToken(token);
    setUser(authService.getUser());
  };
  
  const logout = () => {
    authService.removeToken();
    setUser(null);
  };
  
  return (
    <AuthContext.Provider
      value={{
        user,
        isAuthenticated: !!user,
        login,
        logout
      }}
    >
      {children}
    </AuthContext.Provider>
  );
}

export const useAuth = () => {
  const context = useContext(AuthContext);
  if (!context) {
    throw new Error('useAuth must be used within AuthProvider');
  }
  return context;
};

5. Защита маршрутов

// ProtectedRoute.tsx
interface ProtectedRouteProps {
  children: React.ReactNode;
  requiredRole?: string;
}

export function ProtectedRoute({
  children,
  requiredRole
}: ProtectedRouteProps) {
  const { isAuthenticated, user } = useAuth();
  
  if (!isAuthenticated) {
    return <Navigate to="/login" />;
  }
  
  if (requiredRole && user?.role !== requiredRole) {
    return <Navigate to="/unauthorized" />;
  }
  
  return <>{children}</>;
}

// Использование:
<Router>
  <Routes>
    <Route path="/login" element={<LoginPage />} />
    <Route
      path="/dashboard"
      element={
        <ProtectedRoute>
          <Dashboard />
        </ProtectedRoute>
      }
    />
    <Route
      path="/admin"
      element={
        <ProtectedRoute requiredRole="admin">
          <AdminPanel />
        </ProtectedRoute>
      }
    />
  </Routes>
</Router>

6. Обновление токена (Refresh Token)

// JWT часто имеет короткий срок действия
// Для обновления используют refresh token

const refreshAccessToken = async () => {
  const response = await fetch('/api/auth/refresh', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      refreshToken: localStorage.getItem('refresh_token')
    })
  });
  
  if (!response.ok) {
    // Refresh не удалось - требуется повторная авторизация
    window.location.href = '/login';
    return;
  }
  
  const { token } = await response.json();
  authService.setToken(token);
};

// Проверка и обновление перед каждым запросом
const apiCall = async (url: string, options: RequestInit = {}) => {
  let token = authService.getToken();
  
  // Если токен скоро истечет, обновляем
  if (!authService.isTokenValid()) {
    await refreshAccessToken();
    token = authService.getToken();
  }
  
  return fetch(url, {
    ...options,
    headers: {
      ...options.headers,
      'Authorization': `Bearer ${token}`
    }
  });
};

7. Безопасность JWT

// ХОРОШО:
// 1. Отправляй токен только через HTTPS
// 2. Используй httpOnly cookies для хранения
// 3. Имей короткий срок действия (15-30 минут)
// 4. Используй refresh token для обновления
// 5. Проверяй подпись на сервере

// ПЛОХО:
// 1. Хранить токен в localStorage (уязвимо к XSS)
// 2. Передавать в URL параметрах
// 3. Не проверять срок действия
// 4. Использовать слабый secret key
// 5. Игнорировать HTTPS

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

Как работает авторизация через JWT? | PrepBro