# OAuth Integration Guide ## 🎯 Обзор Система OAuth интеграции с поддержкой популярных провайдеров. Токены хранятся в Redis с автоматическим TTL и поддержкой refresh. ## 🚀 Быстрый старт ### Поддерживаемые провайдеры - **Google** ✅ - OpenID Connect (актуальные endpoints) - **GitHub** ✅ - OAuth 2.0 (scope: read:user user:email) - **Facebook** ✅ - Facebook Login API v18.0+ (scope: email public_profile) - **VK** ✅ - VK OAuth API v5.199+ (scope: email) - **X (Twitter)** ✅ - OAuth 2.0 API v2 (scope: tweet.read users.read) - **Yandex** ✅ - Yandex OAuth (scope: login:email login:info login:avatar) - **Telegram** ⚠️ - Telegram Login (специфическая реализация) ### Redis структура ```bash oauth_access:{user_id}:{provider} # Access токены oauth_refresh:{user_id}:{provider} # Refresh токены oauth_state:{state} # OAuth state с TTL 10 минут ``` ### Основные операции ```python from auth.tokens.oauth import OAuthTokenManager oauth = OAuthTokenManager() # Сохранение токенов await oauth.store_oauth_tokens( user_id="123", provider="google", access_token="ya29.a0AfH6SM...", refresh_token="1//04...", expires_in=3600 ) # Получение токена access_data = await oauth.get_token(user_id, "google", "oauth_access") # Отзыв токенов await oauth.revoke_oauth_tokens(user_id, "google") ``` ## 🔧 OAuth Flow ### 1. Инициация OAuth ```python # Frontend const oauth = (provider: string) => { const state = crypto.randomUUID() localStorage.setItem('oauth_state', state) const oauthUrl = `${coreApiUrl}/auth/oauth/${provider}?state=${state}&redirect_uri=${encodeURIComponent(window.location.origin)}` window.location.href = oauthUrl } ``` ### 2. Backend Endpoints #### GET `/oauth/{provider}` ```python @router.get("/auth/oauth/{provider}") async def oauth_redirect( provider: str, state: str, redirect_uri: str, request: Request ): # Валидация провайдера if provider not in SUPPORTED_PROVIDERS: raise HTTPException(status_code=400, detail="Unsupported OAuth provider") # Сохранение state в Redis await store_oauth_state(state, redirect_uri) # Генерация URL провайдера oauth_url = generate_provider_url(provider, state, redirect_uri) return RedirectResponse(url=oauth_url) ``` #### GET `/oauth/{provider}/callback` ```python @router.get("/oauth/{provider}/callback") async def oauth_callback( provider: str, code: str, state: str, request: Request ): # Проверка state stored_data = await get_oauth_state(state) if not stored_data: raise HTTPException(status_code=400, detail="Invalid or expired state") # Обмен code на access_token try: user_data = await exchange_code_for_user_data(provider, code) except OAuthException as e: logger.error(f"OAuth error for {provider}: {e}") return RedirectResponse(url=f"{stored_data['redirect_uri']}?error=oauth_failed") # Поиск/создание пользователя user = await get_or_create_user_from_oauth(provider, user_data) # Генерация JWT токена access_token = generate_jwt_token(user.id) # Редирект обратно на фронтенд redirect_url = f"{stored_data['redirect_uri']}?state={state}&access_token={access_token}" return RedirectResponse(url=redirect_url) ``` ### 3. OAuth State Management ```python import redis from datetime import timedelta redis_client = redis.Redis() async def store_oauth_state( state: str, redirect_uri: str, ttl: timedelta = timedelta(minutes=10) ): """Сохранение OAuth state с TTL""" key = f"oauth_state:{state}" data = { "redirect_uri": redirect_uri, "created_at": datetime.utcnow().isoformat() } await redis_client.setex(key, ttl, json.dumps(data)) async def get_oauth_state(state: str) -> Optional[dict]: """Получение и удаление OAuth state""" key = f"oauth_state:{state}" data = await redis_client.get(key) if data: await redis_client.delete(key) # One-time use return json.loads(data) return None ``` ## 🔐 Провайдеры ### Google OAuth ```python GOOGLE_OAUTH_CONFIG = { "client_id": os.getenv("GOOGLE_CLIENT_ID"), "client_secret": os.getenv("GOOGLE_CLIENT_SECRET"), "server_metadata_url": "https://accounts.google.com/.well-known/openid-configuration", "scope": "openid email profile" } ``` **✅ Преимущества OpenID Connect:** - Автоматическое обнаружение endpoints через `.well-known/openid-configuration` - Поддержка актуальных стандартов безопасности - Автоматические обновления при изменениях Google API ### GitHub OAuth ```python GITHUB_OAUTH_CONFIG = { "client_id": os.getenv("GITHUB_CLIENT_ID"), "client_secret": os.getenv("GITHUB_CLIENT_SECRET"), "auth_url": "https://github.com/login/oauth/authorize", "token_url": "https://github.com/login/oauth/access_token", "user_info_url": "https://api.github.com/user", "scope": "read:user user:email" } ``` **⚠️ Важные требования GitHub:** - Scope `user:email` **обязателен** для получения email адреса - Проверяйте rate limits (5000 запросов/час для авторизованных пользователей) - Используйте `User-Agent` header во всех запросах к API ### Facebook OAuth ```python FACEBOOK_OAUTH_CONFIG = { "client_id": os.getenv("FACEBOOK_APP_ID"), "client_secret": os.getenv("FACEBOOK_APP_SECRET"), "auth_url": "https://www.facebook.com/v18.0/dialog/oauth", "token_url": "https://graph.facebook.com/v18.0/oauth/access_token", "user_info_url": "https://graph.facebook.com/v18.0/me", "scope": "email public_profile", "token_endpoint_auth_method": "client_secret_post" # Требование Facebook } ``` **⚠️ Важные требования Facebook:** - Используйте **минимум API v18.0** - Обязательно настройте **точные Redirect URIs** в Facebook App - Приложение должно быть в режиме **"Live"** для работы с реальными пользователями - **HTTPS обязателен** для production окружения ### VK OAuth ```python VK_OAUTH_CONFIG = { "client_id": os.getenv("VK_APP_ID"), "client_secret": os.getenv("VK_APP_SECRET"), "auth_url": "https://oauth.vk.com/authorize", "token_url": "https://oauth.vk.com/access_token", "user_info_url": "https://api.vk.com/method/users.get", "scope": "email", "api_version": "5.199" # Актуальная версия API } ``` **⚠️ Важные требования VK:** - Используйте **API версию 5.199+** (5.131 устарела) - Scope `email` необходим для получения email адреса - Redirect URI должен **точно совпадать** с настройками в приложении VK - Поддерживаются только HTTPS redirect URI в production ### X (Twitter) OAuth ```python X_OAUTH_CONFIG = { "client_id": os.getenv("X_CLIENT_ID"), "client_secret": os.getenv("X_CLIENT_SECRET"), "auth_url": "https://twitter.com/i/oauth2/authorize", "token_url": "https://api.twitter.com/2/oauth2/token", "user_info_url": "https://api.twitter.com/2/users/me", "scope": "tweet.read users.read" } ``` **⚠️ Важные требования X:** - Используйте **API v2** endpoints - Scope `users.read` обязателен для получения профиля - Email недоступен через публичное API - Требуется верификация приложения для production ### Yandex OAuth ```python YANDEX_OAUTH_CONFIG = { "client_id": os.getenv("YANDEX_CLIENT_ID"), "client_secret": os.getenv("YANDEX_CLIENT_SECRET"), "auth_url": "https://oauth.yandex.ru/authorize", "token_url": "https://oauth.yandex.ru/token", "user_info_url": "https://login.yandex.ru/info", "scope": "login:email login:info login:avatar" } ``` **⚠️ Важные требования Yandex:** - Scope `login:email` для получения email - Scope `login:info` для базовой информации профиля - Scope `login:avatar` для получения аватара - Поддержка только HTTPS redirect URI ### Telegram OAuth ```python TELEGRAM_OAUTH_CONFIG = { "client_id": os.getenv("TELEGRAM_CLIENT_ID"), "client_secret": os.getenv("TELEGRAM_CLIENT_SECRET"), "auth_url": "https://oauth.telegram.org/auth", "token_url": "https://oauth.telegram.org/auth/request", "scope": "read" } ``` **⚠️ Важные требования Telegram:** - Специальная настройка через @BotFather - Email недоступен - используется временный email - Получение номера телефона требует дополнительных разрешений ## 🔒 Безопасность ### TTL и истечение токенов - **Access tokens**: 1 час (настраивается) - **Refresh tokens**: 30 дней - **OAuth state**: 10 минут - **Автоматическая очистка**: Redis удаляет истекшие токены - **Изоляция провайдеров**: Токены разных провайдеров хранятся отдельно ### CSRF Protection ```python def validate_oauth_state(stored_state: str, received_state: str) -> bool: """Проверка OAuth state для защиты от CSRF""" return stored_state == received_state def validate_redirect_uri(uri: str) -> bool: """Валидация redirect_uri для предотвращения открытых редиректов""" allowed_domains = [ "localhost:3000", "discours.io", "new.discours.io" ] parsed = urlparse(uri) return any(domain in parsed.netloc for domain in allowed_domains) ``` ## 💡 Практические примеры ### OAuth Login Flow ```python from auth.oauth import oauth_login, oauth_callback, _create_or_update_user from auth.oauth import oauth_login_http, oauth_callback_http from auth.oauth import store_oauth_state, get_oauth_state # GraphQL resolver для OAuth login async def handle_oauth_login(provider: str, callback_data: dict): """Инициация OAuth авторизации""" return await oauth_login(None, info, provider, callback_data) # HTTP handler для OAuth login async def handle_oauth_login_http(request): """HTTP инициация OAuth авторизации""" return await oauth_login_http(request) # HTTP handler для OAuth callback async def handle_oauth_callback_http(request): """HTTP обработка OAuth callback""" return await oauth_callback_http(request) # Создание/обновление пользователя async def create_user_from_oauth(provider: str, profile: dict): """Создание пользователя из OAuth профиля""" return await _create_or_update_user(provider, profile) # Управление OAuth состоянием await store_oauth_state(state, oauth_data) state_data = await get_oauth_state(state) ``` ### API Integration ```python async def make_oauth_request(user_id: int, provider: str, endpoint: str): """Запрос к API провайдера""" oauth = OAuthTokenManager() # Получаем access token token_data = await oauth.get_token(str(user_id), provider, "oauth_access") if not token_data: raise OAuthTokenMissing() # Делаем запрос headers = {"Authorization": f"Bearer {token_data['token']}"} response = await httpx.get(endpoint, headers=headers) if response.status_code == 401: # Токен истек, требуется повторная авторизация raise OAuthTokenExpired() return response.json() ``` ### Мониторинг токенов ```python async def check_oauth_health(): """Проверка здоровья OAuth системы""" from auth.tokens.monitoring import TokenMonitoring monitoring = TokenMonitoring() stats = await monitoring.get_token_statistics() return { "oauth_tokens": stats["oauth_access_tokens"] + stats["oauth_refresh_tokens"], "memory_usage": stats["memory_usage"] } ``` ## 🔧 Настройка и деплой ### Environment Variables ```bash # Google OAuth GOOGLE_CLIENT_ID=your_google_client_id GOOGLE_CLIENT_SECRET=your_google_client_secret # GitHub OAuth GITHUB_CLIENT_ID=your_github_client_id GITHUB_CLIENT_SECRET=your_github_client_secret # Facebook OAuth FACEBOOK_APP_ID=your_facebook_app_id FACEBOOK_APP_SECRET=your_facebook_app_secret # VK OAuth VK_APP_ID=your_vk_app_id VK_APP_SECRET=your_vk_app_secret # X (Twitter) OAuth X_CLIENT_ID=your_x_client_id X_CLIENT_SECRET=your_x_client_secret # Yandex OAuth YANDEX_CLIENT_ID=your_yandex_client_id YANDEX_CLIENT_SECRET=your_yandex_client_secret # Telegram OAuth TELEGRAM_CLIENT_ID=your_telegram_client_id TELEGRAM_CLIENT_SECRET=your_telegram_client_secret # HTTPS настройки HTTPS_ENABLED=true # false для разработки # Redis для state management REDIS_URL=redis://localhost:6379/0 # JWT JWT_SECRET=your_jwt_secret_key JWT_EXPIRATION_HOURS=24 ``` ### Настройка провайдеров #### Google OAuth 1. Перейти в [Google Cloud Console](https://console.cloud.google.com/) 2. Создать новый проект или выбрать существующий 3. Включить Google+ API 4. Настроить OAuth consent screen 5. Создать OAuth 2.0 credentials 6. Добавить redirect URIs: - `https://your-domain.com/auth/oauth/google/callback` - `http://localhost:3000/auth/oauth/google/callback` (для разработки) #### GitHub OAuth 1. Перейти в [GitHub Settings](https://github.com/settings/applications/new) 2. Создать новое OAuth App 3. Настроить Authorization callback URL: - `https://your-domain.com/auth/oauth/github/callback` #### Facebook OAuth 1. Перейти в [Facebook Developers](https://developers.facebook.com/) 2. Создать новое приложение 3. Добавить продукт "Facebook Login" 4. Настроить Valid OAuth Redirect URIs: - `https://your-domain.com/oauth/facebook/callback` 5. Переключить приложение в режим "Live" #### X (Twitter) OAuth 1. Перейти в [Twitter Developer Portal](https://developer.twitter.com/en/portal/dashboard) 2. Создать новое приложение 3. Настроить OAuth 2.0 settings 4. Добавить Callback URLs: - `https://your-domain.com/oauth/x/callback` 5. Получить Client ID и Client Secret #### VK OAuth 1. Перейти в [VK Developers](https://vk.com/dev) 2. Создать новое приложение типа "Веб-сайт" 3. Настроить "Доверенный redirect URI": - `https://your-domain.com/oauth/vk/callback` 4. Получить ID приложения и Защищённый ключ #### Yandex OAuth 1. Перейти в [Yandex OAuth](https://oauth.yandex.ru/) 2. Создать новое приложение 3. Настроить Callback URL: - `https://your-domain.com/oauth/yandex/callback` 4. Выбрать необходимые права доступа 5. Получить ID и пароль приложения #### Telegram OAuth 1. Создать бота через @BotFather 2. Получить Bot Token 3. Настроить OAuth через Telegram API 4. **Внимание**: Telegram OAuth имеет специфическую реализацию ### Redis команды для отладки ```bash # Поиск OAuth токенов пользователя redis-cli --scan --pattern "oauth_access:123:*" redis-cli --scan --pattern "oauth_refresh:123:*" # Получение данных токена redis-cli GET "oauth_access:123:google" # Проверка TTL redis-cli TTL "oauth_access:123:google" # Поиск OAuth state redis-cli --scan --pattern "oauth_state:*" ``` ## 🧪 Тестирование ### Unit Tests ```python def test_oauth_redirect(): response = client.get("/auth/oauth/google?state=test&redirect_uri=http://localhost:3000") assert response.status_code == 307 assert "accounts.google.com" in response.headers["location"] def test_oauth_callback(): # Mock provider response with mock.patch('oauth.exchange_code_for_user_data') as mock_exchange: mock_exchange.return_value = OAuthUser( provider="google", provider_id="123456", email="test@example.com", name="Test User" ) response = client.get("/auth/oauth/google/callback?code=test_code&state=test_state") assert response.status_code == 307 assert "access_token=" in response.headers["location"] ``` ### E2E Tests ```typescript // tests/oauth.spec.ts test('OAuth flow with Google', async ({ page }) => { await page.goto('/login') // Click Google OAuth button await page.click('[data-testid="oauth-google"]') // Should redirect to Google await page.waitForURL(/accounts\.google\.com/) // Mock successful OAuth (in test environment) await page.goto('/?state=test&access_token=mock_token') // Should be logged in await expect(page.locator('[data-testid="user-menu"]')).toBeVisible() }) ``` ## 🔧 Troubleshooting ### Частые ошибки 1. **"OAuth state mismatch"** - Проверьте TTL Redis - Убедитесь, что state генерируется правильно 2. **"Provider authentication failed"** - Проверьте client_id и client_secret - Убедитесь, что redirect_uri совпадает с настройками провайдера 3. **"Invalid redirect URI"** - Добавьте все возможные redirect URIs в настройки приложения - Проверьте HTTPS/HTTP в production/development ### Логи для отладки ```bash # Backend логи tail -f /var/log/app/oauth.log | grep "oauth" # Frontend логи (browser console) # Фильтр: "[oauth]" или "[SessionProvider]" ``` ## 📊 Мониторинг ```python # Добавить метрики для мониторинга from prometheus_client import Counter, Histogram oauth_requests = Counter('oauth_requests_total', 'OAuth requests', ['provider', 'status']) oauth_duration = Histogram('oauth_duration_seconds', 'OAuth request duration') @router.get("/{provider}") async def oauth_redirect(provider: str, state: str, redirect_uri: str): with oauth_duration.time(): try: # OAuth logic oauth_requests.labels(provider=provider, status='success').inc() except Exception as e: oauth_requests.labels(provider=provider, status='error').inc() raise ```