2025-09-28 12:22:37 +03:00
|
|
|
|
# 🔐 OAuth Integration Guide
|
2025-09-22 00:56:36 +03:00
|
|
|
|
|
|
|
|
|
|
## 🎯 Обзор
|
|
|
|
|
|
|
2025-09-28 13:06:03 +03:00
|
|
|
|
Система OAuth интеграции с **Bearer токенами** для основного сайта. Поддержка популярных провайдеров с cross-origin совместимостью.
|
2025-09-28 12:22:37 +03:00
|
|
|
|
|
2025-09-28 13:06:03 +03:00
|
|
|
|
**Важно:** OAuth доступен только для основного сайта. Админка использует только email/password аутентификацию.
|
|
|
|
|
|
|
|
|
|
|
|
### 🔄 **Архитектура: стандартный подход**
|
2025-09-28 12:22:37 +03:00
|
|
|
|
|
|
|
|
|
|
```mermaid
|
|
|
|
|
|
sequenceDiagram
|
|
|
|
|
|
participant U as User
|
|
|
|
|
|
participant F as Frontend
|
|
|
|
|
|
participant B as Backend
|
|
|
|
|
|
participant P as OAuth Provider
|
|
|
|
|
|
|
2025-09-28 13:06:03 +03:00
|
|
|
|
U->>F: Click "Login with Provider"
|
|
|
|
|
|
F->>B: GET /oauth/{provider}/login
|
2025-09-28 12:22:37 +03:00
|
|
|
|
B->>P: Redirect to Provider
|
|
|
|
|
|
P->>U: Show authorization page
|
|
|
|
|
|
U->>P: Grant permission
|
2025-09-28 13:06:03 +03:00
|
|
|
|
P->>B: GET /oauth/{provider}/callback?code={code}
|
2025-09-28 12:22:37 +03:00
|
|
|
|
B->>P: Exchange code for token
|
|
|
|
|
|
P->>B: Return access token + user data
|
2025-09-28 13:06:03 +03:00
|
|
|
|
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
|
2025-09-22 00:56:36 +03:00
|
|
|
|
```
|
|
|
|
|
|
|
2025-09-28 12:22:37 +03:00
|
|
|
|
## 🚀 Поддерживаемые провайдеры
|
2025-09-22 00:56:36 +03:00
|
|
|
|
|
2025-09-28 12:22:37 +03:00
|
|
|
|
| Провайдер | Статус | Особенности |
|
|
|
|
|
|
|-----------|--------|-------------|
|
|
|
|
|
|
| **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 |
|
2025-09-22 00:56:36 +03:00
|
|
|
|
|
|
|
|
|
|
## 🔧 OAuth Flow
|
|
|
|
|
|
|
2025-09-28 12:22:37 +03:00
|
|
|
|
### 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`;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Использование
|
|
|
|
|
|
<button onClick={() => handleOAuthLogin('google')}>
|
|
|
|
|
|
🔐 Войти через Google
|
|
|
|
|
|
</button>
|
2025-09-22 00:56:36 +03:00
|
|
|
|
```
|
|
|
|
|
|
|
2025-09-28 12:22:37 +03:00
|
|
|
|
### 2. 🔄 Backend Endpoints
|
2025-09-22 00:56:36 +03:00
|
|
|
|
|
2025-09-28 12:22:37 +03:00
|
|
|
|
#### GET `/oauth/{provider}/login` - Старт OAuth
|
2025-09-26 21:03:45 +03:00
|
|
|
|
```python
|
2025-09-28 12:22:37 +03:00
|
|
|
|
# /oauth/github/login
|
2025-09-26 21:03:45 +03:00
|
|
|
|
# 1. Сохраняет redirect_uri из Referer header в Redis state
|
2025-09-28 12:22:37 +03:00
|
|
|
|
# 2. Генерирует PKCE challenge для безопасности
|
|
|
|
|
|
# 3. Редиректит на провайдера с параметрами авторизации
|
2025-09-26 21:03:45 +03:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### GET `/oauth/{provider}/callback` - Callback
|
2025-09-22 00:56:36 +03:00
|
|
|
|
```python
|
2025-09-28 12:22:37 +03:00
|
|
|
|
# GitHub → /oauth/github/callback?code=xxx&state=yyy
|
|
|
|
|
|
# 1. Валидирует state (CSRF защита)
|
|
|
|
|
|
# 2. Обменивает code на access_token
|
|
|
|
|
|
# 3. Получает профиль пользователя
|
|
|
|
|
|
# 4. Создает/обновляет пользователя в БД
|
|
|
|
|
|
# 5. Создает JWT сессию
|
|
|
|
|
|
# 6. Устанавливает httpOnly cookie
|
|
|
|
|
|
# 7. Редиректит на фронтенд БЕЗ токена в URL
|
2025-09-26 21:03:45 +03:00
|
|
|
|
```
|
|
|
|
|
|
|
2025-09-28 12:22:37 +03:00
|
|
|
|
### 3. 🌐 Фронтенд финализация
|
2025-09-26 21:03:45 +03:00
|
|
|
|
|
2025-09-28 12:22:37 +03:00
|
|
|
|
```typescript
|
2025-09-28 13:06:03 +03:00
|
|
|
|
// OAuth callback route
|
2025-09-28 12:22:37 +03:00
|
|
|
|
export default function OAuthCallback() {
|
|
|
|
|
|
const navigate = useNavigate();
|
|
|
|
|
|
const auth = useAuth();
|
|
|
|
|
|
|
|
|
|
|
|
onMount(async () => {
|
|
|
|
|
|
const urlParams = new URLSearchParams(window.location.search);
|
2025-09-28 13:06:03 +03:00
|
|
|
|
const token = urlParams.get('access_token');
|
2025-09-28 12:22:37 +03:00
|
|
|
|
const error = urlParams.get('error');
|
|
|
|
|
|
|
|
|
|
|
|
if (error) {
|
|
|
|
|
|
// ❌ Ошибка OAuth
|
|
|
|
|
|
console.error('OAuth error:', error);
|
2025-09-28 13:06:03 +03:00
|
|
|
|
navigate('/login?error=' + error);
|
|
|
|
|
|
} else if (token) {
|
|
|
|
|
|
// ✅ Успех! Сохраняем токен в localStorage
|
|
|
|
|
|
localStorage.setItem('access_token', token);
|
2025-09-28 12:22:37 +03:00
|
|
|
|
|
2025-09-28 13:06:03 +03:00
|
|
|
|
// Очищаем URL от токена
|
|
|
|
|
|
window.history.replaceState({}, '', window.location.pathname);
|
2025-09-28 12:22:37 +03:00
|
|
|
|
|
2025-09-28 13:06:03 +03:00
|
|
|
|
// Возвращаемся на сохраненную страницу
|
|
|
|
|
|
const returnUrl = localStorage.getItem('oauth_return_url') || '/';
|
|
|
|
|
|
localStorage.removeItem('oauth_return_url');
|
|
|
|
|
|
navigate(returnUrl);
|
2025-09-28 12:22:37 +03:00
|
|
|
|
} else {
|
2025-09-28 13:06:03 +03:00
|
|
|
|
navigate('/login?error=no_token');
|
2025-09-28 12:22:37 +03:00
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div class="oauth-callback">
|
|
|
|
|
|
<h2>Завершение авторизации...</h2>
|
|
|
|
|
|
<p>Пожалуйста, подождите...</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
2025-09-26 21:03:45 +03:00
|
|
|
|
```
|
|
|
|
|
|
|
2025-09-28 13:06:03 +03:00
|
|
|
|
### 4. 🔑 Использование Bearer токенов
|
2025-09-22 00:56:36 +03:00
|
|
|
|
|
2025-09-28 12:22:37 +03:00
|
|
|
|
```typescript
|
2025-09-28 13:06:03 +03:00
|
|
|
|
// GraphQL клиент использует Bearer токены из localStorage
|
2025-09-28 12:22:37 +03:00
|
|
|
|
const graphqlRequest = async (query: string, variables?: any) => {
|
2025-09-28 13:06:03 +03:00
|
|
|
|
const token = localStorage.getItem('access_token');
|
|
|
|
|
|
|
2025-09-28 12:22:37 +03:00
|
|
|
|
const response = await fetch('/graphql', {
|
|
|
|
|
|
method: 'POST',
|
2025-09-28 13:06:03 +03:00
|
|
|
|
headers: {
|
|
|
|
|
|
'Content-Type': 'application/json',
|
|
|
|
|
|
'Authorization': `Bearer ${token}` // ✅ Bearer токен из localStorage
|
|
|
|
|
|
},
|
2025-09-28 12:22:37 +03:00
|
|
|
|
body: JSON.stringify({ query, variables })
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
return response.json();
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Auth Context
|
|
|
|
|
|
export const AuthProvider = (props: { children: JSX.Element }) => {
|
|
|
|
|
|
const [user, setUser] = createSignal<User | null>(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);
|
2025-09-22 00:56:36 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-28 12:22:37 +03:00
|
|
|
|
setUser(null);
|
|
|
|
|
|
window.location.href = '/';
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Проверяем сессию при загрузке
|
|
|
|
|
|
onMount(() => checkSession());
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<AuthContext.Provider value={{
|
|
|
|
|
|
user,
|
|
|
|
|
|
isAuthenticated: () => !!user(),
|
|
|
|
|
|
checkSession,
|
|
|
|
|
|
logout,
|
|
|
|
|
|
}}>
|
|
|
|
|
|
{props.children}
|
|
|
|
|
|
</AuthContext.Provider>
|
|
|
|
|
|
);
|
|
|
|
|
|
};
|
2025-09-22 00:56:36 +03:00
|
|
|
|
```
|
|
|
|
|
|
|
2025-09-28 12:22:37 +03:00
|
|
|
|
## 🔐 Настройка провайдеров
|
2025-09-22 00:56:36 +03:00
|
|
|
|
|
2025-09-28 12:22:37 +03:00
|
|
|
|
### 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`
|
2025-09-22 23:56:04 +03:00
|
|
|
|
|
2025-09-28 12:22:37 +03:00
|
|
|
|
```bash
|
|
|
|
|
|
GOOGLE_CLIENT_ID=your_google_client_id
|
|
|
|
|
|
GOOGLE_CLIENT_SECRET=your_google_client_secret
|
2025-09-22 00:56:36 +03:00
|
|
|
|
```
|
|
|
|
|
|
|
2025-09-28 12:22:37 +03:00
|
|
|
|
### 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`
|
2025-09-22 23:56:04 +03:00
|
|
|
|
|
2025-09-28 12:22:37 +03:00
|
|
|
|
```bash
|
|
|
|
|
|
GITHUB_CLIENT_ID=your_github_client_id
|
|
|
|
|
|
GITHUB_CLIENT_SECRET=your_github_client_secret
|
2025-09-22 23:56:04 +03:00
|
|
|
|
```
|
|
|
|
|
|
|
2025-09-28 12:22:37 +03:00
|
|
|
|
### 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`
|
2025-09-23 17:14:47 +03:00
|
|
|
|
|
2025-09-28 12:22:37 +03:00
|
|
|
|
```bash
|
|
|
|
|
|
YANDEX_CLIENT_ID=your_yandex_client_id
|
|
|
|
|
|
YANDEX_CLIENT_SECRET=your_yandex_client_secret
|
2025-09-22 00:56:36 +03:00
|
|
|
|
```
|
|
|
|
|
|
|
2025-09-28 12:22:37 +03:00
|
|
|
|
### VK OAuth
|
|
|
|
|
|
1. [VK Developers](https://dev.vk.com/apps)
|
|
|
|
|
|
2. **Создать приложение** → **Веб-сайт**
|
|
|
|
|
|
3. **Redirect URI**: `https://your-domain.com/oauth/vk/callback`
|
2025-09-23 17:14:47 +03:00
|
|
|
|
|
2025-09-28 12:22:37 +03:00
|
|
|
|
```bash
|
|
|
|
|
|
VK_CLIENT_ID=your_vk_app_id
|
|
|
|
|
|
VK_CLIENT_SECRET=your_vk_secure_key
|
2025-09-23 17:14:47 +03:00
|
|
|
|
```
|
|
|
|
|
|
|
2025-09-28 12:22:37 +03:00
|
|
|
|
## 🛡️ Безопасность
|
2025-09-23 17:14:47 +03:00
|
|
|
|
|
2025-09-28 12:22:37 +03:00
|
|
|
|
### httpOnly Cookie настройки
|
2025-09-23 17:14:47 +03:00
|
|
|
|
```python
|
2025-09-28 12:22:37 +03:00
|
|
|
|
# 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 дней
|
2025-09-22 00:56:36 +03:00
|
|
|
|
```
|
|
|
|
|
|
|
2025-09-28 12:22:37 +03:00
|
|
|
|
### CSRF Protection
|
|
|
|
|
|
- **State parameter**: Криптографически стойкий state для каждого запроса
|
|
|
|
|
|
- **PKCE**: Code challenge для дополнительной защиты
|
|
|
|
|
|
- **Redirect URI validation**: Проверка разрешенных доменов
|
2025-09-22 00:56:36 +03:00
|
|
|
|
|
2025-09-28 12:22:37 +03:00
|
|
|
|
### TTL и истечение
|
|
|
|
|
|
- **OAuth state**: 10 минут (одноразовое использование)
|
|
|
|
|
|
- **Session tokens**: 30 дней (настраивается)
|
2025-09-22 00:56:36 +03:00
|
|
|
|
- **Автоматическая очистка**: Redis удаляет истекшие токены
|
|
|
|
|
|
|
2025-09-28 12:22:37 +03:00
|
|
|
|
## 🔧 API для разработчиков
|
|
|
|
|
|
|
|
|
|
|
|
### Проверка OAuth токенов
|
2025-09-22 00:56:36 +03:00
|
|
|
|
```python
|
2025-09-28 12:22:37 +03:00
|
|
|
|
from auth.tokens.oauth import OAuthTokenManager
|
2025-09-22 00:56:36 +03:00
|
|
|
|
|
2025-09-28 12:22:37 +03:00
|
|
|
|
oauth = OAuthTokenManager()
|
2025-09-22 00:56:36 +03:00
|
|
|
|
|
2025-09-28 12:22:37 +03:00
|
|
|
|
# Сохранение OAuth токенов (для API интеграций)
|
|
|
|
|
|
await oauth.store_oauth_tokens(
|
|
|
|
|
|
user_id="123",
|
|
|
|
|
|
provider="google",
|
|
|
|
|
|
access_token="ya29.a0AfH6SM...",
|
|
|
|
|
|
refresh_token="1//04...",
|
|
|
|
|
|
expires_in=3600
|
|
|
|
|
|
)
|
2025-09-22 00:56:36 +03:00
|
|
|
|
|
2025-09-28 12:22:37 +03:00
|
|
|
|
# Получение токена для API вызовов
|
|
|
|
|
|
token_data = await oauth.get_token("123", "google", "oauth_access")
|
|
|
|
|
|
if token_data:
|
|
|
|
|
|
# Используем токен для вызовов Google API
|
2025-09-22 00:56:36 +03:00
|
|
|
|
headers = {"Authorization": f"Bearer {token_data['token']}"}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2025-09-28 12:22:37 +03:00
|
|
|
|
### Redis структура
|
2025-09-22 00:56:36 +03:00
|
|
|
|
```bash
|
2025-09-28 12:22:37 +03:00
|
|
|
|
# OAuth токены для API интеграций
|
|
|
|
|
|
oauth_access:{user_id}:{provider} # Access токен
|
|
|
|
|
|
oauth_refresh:{user_id}:{provider} # Refresh токен
|
2025-09-22 00:56:36 +03:00
|
|
|
|
|
2025-09-28 12:22:37 +03:00
|
|
|
|
# OAuth state (временный)
|
|
|
|
|
|
oauth_state:{state} # Данные авторизации (TTL: 10 мин)
|
2025-09-23 17:14:47 +03:00
|
|
|
|
|
2025-09-28 12:22:37 +03:00
|
|
|
|
# Сессии пользователей (основные)
|
|
|
|
|
|
session:{user_id}:{token} # JWT сессия (TTL: 30 дней)
|
|
|
|
|
|
```
|
2025-09-23 17:14:47 +03:00
|
|
|
|
|
2025-09-28 12:22:37 +03:00
|
|
|
|
## 🧪 Тестирование
|
2025-09-22 00:56:36 +03:00
|
|
|
|
|
2025-09-28 12:22:37 +03:00
|
|
|
|
### 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();
|
|
|
|
|
|
});
|
2025-09-22 00:56:36 +03:00
|
|
|
|
```
|
|
|
|
|
|
|
2025-09-28 12:22:37 +03:00
|
|
|
|
### Отладка
|
2025-09-22 00:56:36 +03:00
|
|
|
|
```bash
|
2025-09-28 12:22:37 +03:00
|
|
|
|
# Проверка OAuth провайдеров
|
|
|
|
|
|
curl -v "https://your-domain.com/oauth/google/login"
|
2025-09-22 00:56:36 +03:00
|
|
|
|
|
2025-09-28 12:22:37 +03:00
|
|
|
|
# Проверка callback
|
|
|
|
|
|
curl -v "https://your-domain.com/oauth/google/callback?code=test&state=test"
|
2025-09-22 00:56:36 +03:00
|
|
|
|
|
2025-09-28 12:22:37 +03:00
|
|
|
|
# Проверка сессии с cookie
|
|
|
|
|
|
curl -b "session_token=your_token" "https://your-domain.com/graphql" \
|
|
|
|
|
|
-d '{"query":"query { getSession { success author { id } } }"}'
|
2025-09-22 00:56:36 +03:00
|
|
|
|
```
|
|
|
|
|
|
|
2025-09-28 12:22:37 +03:00
|
|
|
|
## 📊 Мониторинг
|
2025-09-22 00:56:36 +03:00
|
|
|
|
|
|
|
|
|
|
```python
|
2025-09-28 12:22:37 +03:00
|
|
|
|
from auth.tokens.monitoring import TokenMonitoring
|
2025-09-22 00:56:36 +03:00
|
|
|
|
|
2025-09-28 12:22:37 +03:00
|
|
|
|
monitoring = TokenMonitoring()
|
2025-09-22 00:56:36 +03:00
|
|
|
|
|
2025-09-28 12:22:37 +03:00
|
|
|
|
# Статистика 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}")
|
2025-09-22 00:56:36 +03:00
|
|
|
|
|
2025-09-28 12:22:37 +03:00
|
|
|
|
# Health check
|
|
|
|
|
|
health = await monitoring.health_check()
|
|
|
|
|
|
if health["status"] == "healthy":
|
|
|
|
|
|
print("✅ OAuth system is healthy")
|
2025-09-22 00:56:36 +03:00
|
|
|
|
```
|
|
|
|
|
|
|
2025-09-28 12:22:37 +03:00
|
|
|
|
## 🎯 Преимущества новой архитектуры
|
2025-09-22 00:56:36 +03:00
|
|
|
|
|
2025-09-28 12:22:37 +03:00
|
|
|
|
### 🛡️ Максимальная безопасность:
|
|
|
|
|
|
- **🚫 Защита от XSS**: Токены недоступны JavaScript
|
|
|
|
|
|
- **🔒 Защита от CSRF**: SameSite cookies
|
|
|
|
|
|
- **🛡️ Единообразие**: Все провайдеры используют один механизм
|
2025-09-22 00:56:36 +03:00
|
|
|
|
|
2025-09-28 12:22:37 +03:00
|
|
|
|
### 🚀 Простота использования:
|
|
|
|
|
|
- **📱 Автоматическая отправка**: Браузер сам включает cookies
|
|
|
|
|
|
- **🧹 Чистый код**: Нет управления токенами в JavaScript
|
|
|
|
|
|
- **🔄 Единый API**: Один GraphQL клиент для всех случаев
|
2025-09-22 00:56:36 +03:00
|
|
|
|
|
2025-09-28 12:22:37 +03:00
|
|
|
|
### ⚡ Производительность:
|
|
|
|
|
|
- **🚀 Быстрее**: Нет localStorage операций
|
|
|
|
|
|
- **📦 Меньше кода**: Упрощенная логика фронтенда
|
|
|
|
|
|
- **🔄 Автоматическое управление**: Браузер оптимизирует отправку cookies
|
2025-09-22 00:56:36 +03:00
|
|
|
|
|
2025-09-28 12:22:37 +03:00
|
|
|
|
**Результат: Самая безопасная и простая OAuth интеграция!** 🔐✨
|