← Назад к вопросам
Как сделать hooks-based авторизацию не полагаясь на hook?
2.3 Middle🔥 231 комментариев
#React#Архитектура и паттерны
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI3 апр. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Как сделать hooks-based авторизацию не полагаясь на hook?
Вопрос о реализации авторизации без хуков - это вопрос об архитектуре и разделении ответственности. На самом деле, правильно организованная система авторизации должна быть независима от React и хуков, работая на низком уровне.
Правильная архитектура авторизации
// 1. Слой сервиса (не зависит от React)
// src/services/auth.service.ts
interface User {
id: string;
name: string;
email: string;
role: string;
}
class AuthService {
private currentUser: User | null = null;
private token: string | null = null;
private listeners: Set<(user: User | null) => void> = new Set();
// Отправляем токен при инициализации
async initialize() {
const token = localStorage.getItem("authToken");
if (token) {
try {
const response = await fetch("/api/auth/me", {
headers: { Authorization: `Bearer ${token}` }
});
if (response.ok) {
this.currentUser = await response.json();
this.token = token;
this.notifyListeners();
} else {
this.logout();
}
} catch (error) {
this.logout();
}
}
}
async login(email: string, password: string): Promise<User> {
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 { user, token } = await response.json();
this.currentUser = user;
this.token = token;
localStorage.setItem("authToken", token);
this.notifyListeners();
return user;
}
async logout() {
this.currentUser = null;
this.token = null;
localStorage.removeItem("authToken");
this.notifyListeners();
}
getUser(): User | null {
return this.currentUser;
}
getToken(): string | null {
return this.token;
}
isAuthenticated(): boolean {
return this.currentUser !== null;
}
hasRole(role: string): boolean {
return this.currentUser?.role === role;
}
// Подписка на изменения авторизации
subscribe(listener: (user: User | null) => void) {
this.listeners.add(listener);
return () => this.listeners.delete(listener);
}
private notifyListeners() {
this.listeners.forEach(listener => listener(this.currentUser));
}
}
export const authService = new AuthService();
Подготовка токена в HTTP запросах
// src/lib/api.ts - вспомогательная функция
import { authService } from "@/services/auth.service";
class ApiClient {
async fetch(url: string, options: RequestInit = {}) {
const headers = {
...options.headers
} as HeadersInit;
const token = authService.getToken();
if (token) {
headers["Authorization"] = `Bearer ${token}`;
}
const response = await fetch(url, { ...options, headers });
// Если 401 - токен истёк
if (response.status === 401) {
authService.logout();
window.location.href = "/login";
}
return response;
}
}
export const api = new ApiClient();
React Context для подключения к сервису
// src/contexts/auth.context.tsx
import React, { useEffect, useState } from "react";
import { authService } from "@/services/auth.service";
interface AuthContextType {
user: User | null;
isLoading: boolean;
isAuthenticated: boolean;
login: (email: string, password: string) => Promise<void>;
logout: () => Promise<void>;
}
const AuthContext = React.createContext<AuthContextType>({
user: null,
isLoading: true,
isAuthenticated: false,
login: async () => {},
logout: async () => {}
});
export function AuthProvider({ children }: { children: React.ReactNode }) {
const [user, setUser] = useState<User | null>(null);
const [isLoading, setIsLoading] = useState(true);
// Инициализируем сервис при mount
useEffect(() => {
const initAuth = async () => {
await authService.initialize();
setUser(authService.getUser());
setIsLoading(false);
};
initAuth();
}, []);
// Подписываемся на изменения авторизации
useEffect(() => {
const unsubscribe = authService.subscribe((updatedUser) => {
setUser(updatedUser);
});
return unsubscribe;
}, []);
const value: AuthContextType = {
user,
isLoading,
isAuthenticated: user !== null,
login: async (email, password) => {
await authService.login(email, password);
},
logout: async () => {
await authService.logout();
}
};
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
}
// Хук для доступа к контексту
export function useAuth() {
const context = React.useContext(AuthContext);
if (!context) {
throw new Error("useAuth должен использоваться в AuthProvider");
}
return context;
}
Использование без хуков - в компонентах
// Класс компонент тоже работает
class Dashboard extends React.Component {
state = {
user: null,
loading: true
};
componentDidMount() {
// Получаем данные из сервиса (не зависит от хуков)
this.setState({
user: authService.getUser(),
loading: false
});
// Подписываемся на изменения
this.unsubscribe = authService.subscribe((user) => {
this.setState({ user });
});
}
componentWillUnmount() {
this.unsubscribe?.();
}
render() {
const { user, loading } = this.state;
if (loading) return <div>Loading...</div>;
if (!user) return <div>Not authenticated</div>;
return <div>Welcome, {user.name}</div>;
}
}
Защита роутов без React Router хуков
// src/guards/auth.guard.tsx
import { ReactNode } from "react";
import { authService } from "@/services/auth.service";
import { useAuth } from "@/contexts/auth.context";
interface ProtectedRouteProps {
children: ReactNode;
requiredRole?: string;
fallback?: ReactNode;
}
export function ProtectedRoute({
children,
requiredRole,
fallback = <div>Access Denied</div>
}: ProtectedRouteProps) {
const { user, isLoading } = useAuth();
if (isLoading) {
return <div>Loading...</div>;
}
if (!user) {
return <div>Please login</div>;
}
if (requiredRole && !authService.hasRole(requiredRole)) {
return fallback;
}
return <>{children}</>;
}
Работа с авторизацией без хуков - в утилитах
// src/lib/auth-utils.ts
import { authService } from "@/services/auth.service";
// Проверка авторизации без React
export function isUserLoggedIn(): boolean {
return authService.isAuthenticated();
}
// Получение текущего пользователя
export function getCurrentUser() {
return authService.getUser();
}
// Получение токена для запросов
export function getAuthToken(): string | null {
return authService.getToken();
}
// Проверка прав
export function hasPermission(role: string): boolean {
return authService.hasRole(role);
}
// Использование в любом месте приложения
if (isUserLoggedIn()) {
console.log("User:", getCurrentUser());
}
Интеграция с middleware и interceptors
// src/lib/api-interceptor.ts
import { authService } from "@/services/auth.service";
// Переопределяем fetch глобально
const originalFetch = window.fetch;
window.fetch = async (input: RequestInfo, init?: RequestInit) => {
const headers = {
...init?.headers
} as HeadersInit;
// Добавляем токен в каждый запрос
const token = authService.getToken();
if (token) {
headers["Authorization"] = `Bearer ${token}`;
}
let response = await originalFetch(input, { ...init, headers });
// Если 401 - пытаемся обновить токен
if (response.status === 401) {
await authService.logout();
// Или пытаемся refresh token
// const refreshed = await authService.refreshToken();
// if (refreshed) {
// return window.fetch(input, init); // повторный запрос
// }
}
return response;
};
Next.js специфичный подход
// src/lib/auth.ts - для использования везде
import { cookies } from "next/headers";
export async function getAuthToken() {
const cookieStore = await cookies();
return cookieStore.get("authToken")?.value || null;
}
export async function getCurrentUser() {
const token = await getAuthToken();
if (!token) return null;
try {
const response = await fetch("http://localhost:3000/api/auth/me", {
headers: { Authorization: `Bearer ${token}` }
});
if (response.ok) {
return await response.json();
}
} catch (error) {
console.error("Failed to get user", error);
}
return null;
}
// Использование в server components
export default async function Dashboard() {
const user = await getCurrentUser();
if (!user) {
return <div>Not authenticated</div>;
}
return <div>Welcome, {user.name}</div>;
}
Итоговая архитектура
src/
services/
auth.service.ts <- Core авторизация (NO React)
lib/
api.ts <- HTTP client с авторизацией
auth-utils.ts <- Утилиты для доступа к auth
contexts/
auth.context.tsx <- React Context обёртка
hooks/
useAuth.ts <- Хук для доступа к контексту
guards/
ProtectedRoute.tsx <- Компоненты для защиты
components/
Header.tsx <- Компоненты используют хуки
Главный принцип
Авторизация - это слой который должен быть независим от React. React должен подписываться на изменения в сервисе авторизации, а не быть его основой. Таким образом вы можете использовать авторизацию везде:
- В React компонентах (с хуками)
- В класс компонентах (без хуков)
- В утилитах и вспомогательных функциях
- В server components (Next.js)
- Даже в обычном JavaScript коде
Это правильное разделение ответственности и делает код более переиспользуемым и тестируемым.