# 🔐 OAuth Integration Guide ## 🎯 Обзор Система OAuth интеграции с **Bearer токенами** для основного сайта. Поддержка популярных провайдеров с cross-origin совместимостью. **Важно:** OAuth доступен только для основного сайта. Админка использует только email/password аутентификацию. ### 🔄 **Архитектура: стандартный подход** ```mermaid sequenceDiagram participant U as User participant F as Frontend participant B as Backend participant P as OAuth Provider U->>F: Click "Login with Provider" F->>B: GET /oauth/{provider}/login B->>P: Redirect to Provider P->>U: Show authorization page U->>P: Grant permission P->>B: GET /oauth/{provider}/callback?code={code} B->>P: Exchange code for token P->>B: Return access token + user data B->>B: Create/update user + JWT session B->>F: Redirect with token in URL Note over B,F: URL: /?access_token=JWT_TOKEN F->>F: Save token to localStorage F->>F: Clear token from URL F->>U: User logged in Note over F,B: All subsequent requests F->>B: GraphQL with Authorization: Bearer ``` ## 🚀 Поддерживаемые провайдеры | Провайдер | Статус | Особенности | |-----------|--------|-------------| | **Google** | ✅ | OpenID Connect, актуальные endpoints | | **GitHub** | ✅ | OAuth 2.0, scope: `read:user user:email` | | **Yandex** | ✅ | OAuth, scope: `login:email login:info` | | **VK** | ✅ | OAuth API v5.199+, scope: `email` | | **Facebook** | ✅ | Facebook Login API v18.0+ | | **X (Twitter)** | ✅ | OAuth 2.0 API v2 | ## 🔧 OAuth Flow ### 1. 🚀 Инициация OAuth (Фронтенд) ```typescript // Простой редирект - backend получит redirect_uri из Referer header const handleOAuthLogin = (provider: string) => { // Сохраняем текущую страницу для возврата localStorage.setItem('oauth_return_url', window.location.pathname); // Редиректим на OAuth endpoint window.location.href = `/oauth/${provider}/login`; }; // Использование ``` ### 2. 🔄 Backend Endpoints #### GET `/oauth/{provider}/login` - Старт OAuth ```python # /oauth/github/login # 1. Сохраняет redirect_uri из Referer header в Redis state # 2. Генерирует PKCE challenge для безопасности # 3. Редиректит на провайдера с параметрами авторизации ``` #### GET `/oauth/{provider}/callback` - Callback ```python # GitHub → /oauth/github/callback?code=xxx&state=yyy # 1. Валидирует state (CSRF защита) # 2. Обменивает code на access_token # 3. Получает профиль пользователя # 4. Создает/обновляет пользователя в БД # 5. Создает JWT сессию # 6. Устанавливает httpOnly cookie # 7. Редиректит на фронтенд БЕЗ токена в URL ``` ### 3. 🌐 Фронтенд финализация ```typescript // OAuth callback route export default function OAuthCallback() { const navigate = useNavigate(); const auth = useAuth(); onMount(async () => { const urlParams = new URLSearchParams(window.location.search); const token = urlParams.get('access_token'); const error = urlParams.get('error'); if (error) { // ❌ Ошибка OAuth console.error('OAuth error:', error); navigate('/login?error=' + error); } else if (token) { // ✅ Успех! Сохраняем токен в localStorage localStorage.setItem('access_token', token); // Очищаем URL от токена window.history.replaceState({}, '', window.location.pathname); // Возвращаемся на сохраненную страницу const returnUrl = localStorage.getItem('oauth_return_url') || '/'; localStorage.removeItem('oauth_return_url'); navigate(returnUrl); } else { navigate('/login?error=no_token'); } }); return (

Завершение авторизации...

Пожалуйста, подождите...

); } ``` ### 4. 🔑 Использование Bearer токенов ```typescript // GraphQL клиент использует Bearer токены из localStorage const graphqlRequest = async (query: string, variables?: any) => { const token = localStorage.getItem('access_token'); const response = await fetch('/graphql', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` // ✅ Bearer токен из localStorage }, body: JSON.stringify({ query, variables }) }); return response.json(); }; // Auth Context export const AuthProvider = (props: { children: JSX.Element }) => { const [user, setUser] = createSignal(null); const checkSession = async () => { try { const response = await graphqlRequest(` query GetSession { getSession { success author { id slug email name } } } `); if (response.data?.getSession?.success) { setUser(response.data.getSession.author); } else { setUser(null); } } catch (error) { console.error('Session check failed:', error); setUser(null); } }; const logout = async () => { try { // Удаляем httpOnly cookie на бэкенде await graphqlRequest(`mutation { logout { success } }`); } catch (error) { console.error('Logout error:', error); } setUser(null); window.location.href = '/'; }; // Проверяем сессию при загрузке onMount(() => checkSession()); return ( !!user(), checkSession, logout, }}> {props.children} ); }; ``` ## 🔐 Настройка провайдеров ### Google OAuth 1. [Google Cloud Console](https://console.cloud.google.com/) 2. **APIs & Services** → **Credentials** → **OAuth 2.0 Client ID** 3. **Authorized redirect URIs**: `https://your-domain.com/oauth/google/callback` ```bash GOOGLE_CLIENT_ID=your_google_client_id GOOGLE_CLIENT_SECRET=your_google_client_secret ``` ### GitHub OAuth 1. [GitHub Developer Settings](https://github.com/settings/developers) 2. **New OAuth App** 3. **Authorization callback URL**: `https://your-domain.com/oauth/github/callback` ```bash GITHUB_CLIENT_ID=your_github_client_id GITHUB_CLIENT_SECRET=your_github_client_secret ``` ### Yandex OAuth 1. [Yandex OAuth](https://oauth.yandex.ru/) 2. **Создать новое приложение** 3. **Callback URI**: `https://your-domain.com/oauth/yandex/callback` 4. **Права**: `login:info`, `login:email`, `login:avatar` ```bash YANDEX_CLIENT_ID=your_yandex_client_id YANDEX_CLIENT_SECRET=your_yandex_client_secret ``` ### VK OAuth 1. [VK Developers](https://dev.vk.com/apps) 2. **Создать приложение** → **Веб-сайт** 3. **Redirect URI**: `https://your-domain.com/oauth/vk/callback` ```bash VK_CLIENT_ID=your_vk_app_id VK_CLIENT_SECRET=your_vk_secure_key ``` ## 🛡️ Безопасность ### httpOnly Cookie настройки ```python # settings.py SESSION_COOKIE_NAME = "session_token" SESSION_COOKIE_HTTPONLY = True # Защита от XSS SESSION_COOKIE_SECURE = True # Только HTTPS SESSION_COOKIE_SAMESITE = "lax" # CSRF защита SESSION_COOKIE_MAX_AGE = 30 * 24 * 60 * 60 # 30 дней ``` ### CSRF Protection - **State parameter**: Криптографически стойкий state для каждого запроса - **PKCE**: Code challenge для дополнительной защиты - **Redirect URI validation**: Проверка разрешенных доменов ### TTL и истечение - **OAuth state**: 10 минут (одноразовое использование) - **Session tokens**: 30 дней (настраивается) - **Автоматическая очистка**: Redis удаляет истекшие токены ## 🔧 API для разработчиков ### Проверка OAuth токенов ```python from auth.tokens.oauth import OAuthTokenManager oauth = OAuthTokenManager() # Сохранение OAuth токенов (для API интеграций) await oauth.store_oauth_tokens( user_id="123", provider="google", access_token="ya29.a0AfH6SM...", refresh_token="1//04...", expires_in=3600 ) # Получение токена для API вызовов token_data = await oauth.get_token("123", "google", "oauth_access") if token_data: # Используем токен для вызовов Google API headers = {"Authorization": f"Bearer {token_data['token']}"} ``` ### Redis структура ```bash # OAuth токены для API интеграций oauth_access:{user_id}:{provider} # Access токен oauth_refresh:{user_id}:{provider} # Refresh токен # OAuth state (временный) oauth_state:{state} # Данные авторизации (TTL: 10 мин) # Сессии пользователей (основные) session:{user_id}:{token} # JWT сессия (TTL: 30 дней) ``` ## 🧪 Тестирование ### E2E Test ```typescript test('OAuth flow with httpOnly cookies', async ({ page }) => { // 1. Инициация OAuth await page.goto('/login'); await page.click('[data-testid="google-login"]'); // 2. Проверяем редирект на Google await expect(page).toHaveURL(/accounts\.google\.com/); // 3. Симулируем успешный callback (в тестовой среде) await page.goto('/oauth/callback'); // 4. Проверяем что cookie установлен const cookies = await page.context().cookies(); const authCookie = cookies.find(c => c.name === 'session_token'); expect(authCookie).toBeTruthy(); expect(authCookie?.httpOnly).toBe(true); // 5. Проверяем что пользователь авторизован await expect(page.locator('[data-testid="user-menu"]')).toBeVisible(); }); ``` ### Отладка ```bash # Проверка OAuth провайдеров curl -v "https://your-domain.com/oauth/google/login" # Проверка callback curl -v "https://your-domain.com/oauth/google/callback?code=test&state=test" # Проверка сессии с cookie curl -b "session_token=your_token" "https://your-domain.com/graphql" \ -d '{"query":"query { getSession { success author { id } } }"}' ``` ## 📊 Мониторинг ```python from auth.tokens.monitoring import TokenMonitoring monitoring = TokenMonitoring() # Статистика OAuth stats = await monitoring.get_token_statistics() oauth_tokens = stats.get("oauth_access_tokens", 0) + stats.get("oauth_refresh_tokens", 0) print(f"OAuth tokens: {oauth_tokens}") # Health check health = await monitoring.health_check() if health["status"] == "healthy": print("✅ OAuth system is healthy") ``` ## 🎯 Преимущества новой архитектуры ### 🛡️ Максимальная безопасность: - **🚫 Защита от XSS**: Токены недоступны JavaScript - **🔒 Защита от CSRF**: SameSite cookies - **🛡️ Единообразие**: Все провайдеры используют один механизм ### 🚀 Простота использования: - **📱 Автоматическая отправка**: Браузер сам включает cookies - **🧹 Чистый код**: Нет управления токенами в JavaScript - **🔄 Единый API**: Один GraphQL клиент для всех случаев ### ⚡ Производительность: - **🚀 Быстрее**: Нет localStorage операций - **📦 Меньше кода**: Упрощенная логика фронтенда - **🔄 Автоматическое управление**: Браузер оптимизирует отправку cookies **Результат: Самая безопасная и простая OAuth интеграция!** 🔐✨