Как на проекте устроена авторизация?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Архитектура авторизации в веб-приложении
Авторизация (authentication) - это процесс проверки личности пользователя и выдачи ему токена для доступа к защищённым ресурсам. Существует множество подходов, и каждый имеет свои преимущества.
Основные методы авторизации
1. Session-based (Сессии)
Классический подход с cookies:
// Клиент отправляет логин/пароль
fetch('https://api.example.com/login', {
method: 'POST',
credentials: 'include', // Отправить cookies
body: JSON.stringify({
email: 'user@example.com',
password: 'secret123'
})
})
.then(res => res.json())
.then(data => {
// Сервер возвращает Set-Cookie: sessionId=abc123
// Браузер автоматически сохранит cookie
// При следующих запросах cookie отправится автоматически
console.log('Авторизирован');
});
// Следующий запрос
fetch('https://api.example.com/profile', {
credentials: 'include' // Отправить сохранённый cookie
})
// Сервер проверит sessionId из cookie
Поток session-based:
1. Клиент: POST /login -> отправляет логин/пароль
2. Сервер: проверяет пароль -> создаёт сессию -> возвращает Set-Cookie
3. Браузер: сохранит cookie
4. Клиент: GET /profile -> отправляет cookie автоматически
5. Сервер: проверяет сессию из cookie -> возвращает данные
Преимущества:
- Простая реализация
- CSRF защита встроена
- Сессия хранится на сервере (безопасно)
Недостатки:
- Масштабируемость (нужно синхронизировать сессии между серверами)
- Мобильные приложения (cookies работают хуже)
2. Token-based (JWT)
Современный подход с токенами:
// Клиент отправляет логин/пароль
fetch('https://api.example.com/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
email: 'user@example.com',
password: 'secret123'
})
})
.then(res => res.json())
.then(data => {
// Сервер возвращает JWT токен
const token = data.access_token;
// Клиент сохранит токен (в localStorage, sessionStorage, или memory)
localStorage.setItem('token', token);
});
// Следующий запрос
fetch('https://api.example.com/profile', {
headers: {
'Authorization': `Bearer ${localStorage.getItem('token')}`
}
})
// Сервер проверит подпись токена
Структура JWT токена:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
// Декодировано:
// Header: {"alg":"HS256","typ":"JWT"}
// Payload: {"sub":"1234567890","name":"John Doe","iat":1516239022}
// Signature: SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
Преимущества:
- Масштабируемость (не нужна синхронизация на сервере)
- Мобильные приложения
- Микросервисная архитектура
Недостатки:
- Токен нельзя отозвать быстро (до истечения)
- XSS уязвимость (если хранить в localStorage)
OAuth 2.0 (Третьих сторон)
Делегированная авторизация через Google, GitHub, Facebook:
// Клиент перенаправляется на Google
window.location.href = `https://accounts.google.com/o/oauth2/v2/auth?` +
`client_id=YOUR_CLIENT_ID&` +
`redirect_uri=https://yourapp.com/callback&` +
`response_type=code&` +
`scope=email profile`;
// После ввода данных на Google,
// браузер перенаправляется на /callback?code=abc123
// На backend:
// POST https://oauth2.googleapis.com/token
// {
// code: 'abc123',
// client_id: 'YOUR_CLIENT_ID',
// client_secret: 'YOUR_SECRET',
// redirect_uri: 'https://yourapp.com/callback'
// }
// Возвращает access_token и refresh_token
Поток OAuth 2.0:
1. Клиент: нажимает "Sign in with Google"
2. Клиент: перенаправляется на google.com
3. Google: пользователь вводит пароль
4. Google: перенаправляет на ваше приложение с кодом
5. Сервер: обменивает код на токен (скрытно)
6. Сервер: возвращает ваш JWT или sessionId
7. Клиент: сохраняет токен
Типичная архитектура для Frontend
Компонент авторизации:
// AuthContext.tsx
import { createContext, useState, useEffect } from 'react';
const AuthContext = createContext();
export function AuthProvider({ children }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [token, setToken] = useState(null);
// Проверить токен при загрузке
useEffect(() => {
const savedToken = localStorage.getItem('token');
if (savedToken) {
validateToken(savedToken);
}
setLoading(false);
}, []);
const validateToken = async (token) => {
try {
const res = await fetch('/api/profile', {
headers: { 'Authorization': `Bearer ${token}` }
});
const data = await res.json();
setUser(data);
setToken(token);
} catch (error) {
localStorage.removeItem('token');
}
};
const login = async (email, password) => {
const res = await fetch('/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password })
});
const data = await res.json();
localStorage.setItem('token', data.access_token);
setToken(data.access_token);
setUser(data.user);
};
const logout = () => {
localStorage.removeItem('token');
setToken(null);
setUser(null);
};
return (
<AuthContext.Provider value={{ user, token, login, logout, loading }}>
{children}
</AuthContext.Provider>
);
}
// useAuth.ts
export function useAuth() {
const context = useContext(AuthContext);
if (!context) {
throw new Error('useAuth must be used within AuthProvider');
}
return context;
}
Защищённый роут:
// ProtectedRoute.tsx
import { useAuth } from '@/hooks/useAuth';
import { Navigate } from 'react-router-dom';
export function ProtectedRoute({ children }) {
const { user, loading } = useAuth();
if (loading) return <div>Loading...</div>;
if (!user) return <Navigate to="/login" />;
return children;
}
// App.tsx
<Routes>
<Route path="/login" element={<LoginPage />} />
<Route path="/profile" element={
<ProtectedRoute>
<ProfilePage />
</ProtectedRoute>
} />
</Routes>
Интерцептор запросов:
// api.ts
const api = axios.create({
baseURL: 'https://api.example.com'
});
// Добавить токен к каждому запросу
api.interceptors.request.use((config) => {
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
// Обновить токен если истёк
api.interceptors.response.use(
(response) => response,
async (error) => {
if (error.response?.status === 401) {
// Токен истёк
const refreshToken = localStorage.getItem('refresh_token');
const res = await fetch('/api/refresh', {
method: 'POST',
body: JSON.stringify({ refresh_token: refreshToken })
});
const data = await res.json();
localStorage.setItem('token', data.access_token);
// Повторить оригинальный запрос
return api.request(error.config);
}
return Promise.reject(error);
}
);
export default api;
Безопасность при авторизации
Хранение токенов:
- localStorage - уязвим для XSS
- sessionStorage - уязвим для XSS
- HTTP-only cookies - защищён от XSS, но нужна CSRF защита
- Memory - теряется при перезагрузке
Best practice:
// Хранить токен в памяти
// Refresh token в HTTP-only cookie
// При перезагрузке обновить токен с помощью refresh token
HTTPS:
// ВСЕГДА использовать HTTPS для авторизации
// Токены в заголовках легко перехватываются по HTTP
CORS:
// POST запросы к auth endpoints должны иметь CORS headers
fetch('https://api.example.com/login', {
method: 'POST',
credentials: 'include', // Если используются cookies
headers: { 'Content-Type': 'application/json' }
});
Заключение
Авторизация - критичный компонент приложения. Главные подходы:
- Sessions - для монолитных приложений
- JWT - для современных микросервисов и мобильных
- OAuth 2.0 - для интеграции с третьими сторонами
Независимо от выбранного метода, приоритет - безопасность и удобство пользователя.