Skip to content

Duckademic/auth-service

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Auth Service - Мікросервіс Автентифікації та Авторизації

Сучасний мікросервіс для централізованої автентифікації та авторизації користувачів у мікросервісній архітектурі. Побудований на FastAPI, PostgreSQL, Redis та OpenFGA.

🏗 Архітектура системи

┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│   Client    │────▶│ API Gateway │────▶│Auth Service │
└─────────────┘     └─────────────┘     └─────────────┘
                                               │
                    ┌──────────────────────────┼──────────────────────────┐
                    │                          │                          │
              ┌─────▼─────┐           ┌───────▼───────┐          ┌───────▼───────┐
              │PostgreSQL │           │     Redis     │          │   OpenFGA     │
              └───────────┘           └───────────────┘          └───────────────┘

🔄 Як працює система (для розробників)

Основний флоу автентифікації

  1. Реєстрація користувача

    • Користувач надсилає дані реєстрації
    • Система перевіряє унікальність email/username
    • Пароль хешується через Argon2id
    • Створюється запис користувача в PostgreSQL
    • Надсилається email для верифікації (якщо увімкнено)
  2. Вхід в систему

    • Користувач вводить email і пароль
    • Система перевіряє credentials
    • Якщо MFA увімкнено - запитує OTP код
    • Генерується JWT access token (15 хв) і refresh token (30 днів)
    • Токени зберігаються в Redis для швидкої валідації
  3. Авторизація запитів

    • Кожен запит містить JWT token в заголовку Authorization
    • Token перевіряється на підпис і термін дії
    • З токена витягується user_id і scopes
    • Для складних прав - запит до OpenFGA
  4. Оновлення токенів

    • Коли access token закінчується, клієнт використовує refresh token
    • Старий refresh token додається в blacklist
    • Видається нова пара токенів

📋 Детальний опис API ендпоінтів

🔐 Автентифікація (/api/v1/auth)

POST /api/v1/auth/register

Що робить: Реєструє нового користувача в системі

Запит:

{
  "email": "user@example.com",
  "username": "john_doe",  // опціонально
  "full_name": "John Doe",  // опціонально
  "password": "SecurePass123!"
}

Відповідь:

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "email": "user@example.com",
  "username": "john_doe",
  "full_name": "John Doe",
  "status": "inactive",  // або "active" якщо email верифікація вимкнена
  "is_email_verified": false,
  "is_superuser": false,
  "mfa_enabled": false,
  "created_at": "2024-01-15T10:30:00Z"
}

Що відбувається всередині:

  1. Валідація даних (пароль мін 8 символів, має великі/малі літери і цифри)
  2. Перевірка чи email/username вже існують
  3. Хешування пароля через Argon2id
  4. Збереження в БД
  5. Відправка email для верифікації (якщо увімкнено)

POST /api/v1/auth/token

Що робить: Вхід користувача, отримання токенів

Запит (OAuth2 формат):

username: user@example.com  # Так, це email, але OAuth2 вимагає поле "username"
password: SecurePass123!
grant_type: password
scope: openid profile email  # опціонально

Відповідь (без MFA):

{
  "access_token": "eyJhbGciOiJSUzI1NiIs...",
  "refresh_token": "eyJhbGciOiJSUzI1NiIs...",
  "token_type": "Bearer",
  "expires_in": 900,  // 15 хвилин в секундах
  "scope": "openid profile email"
}

Відповідь (з MFA):

{
  "access_token": "temp_token_for_mfa",
  "token_type": "mfa_required",
  "expires_in": 300  // 5 хвилин для введення MFA
}

Що відбувається всередині:

  1. Пошук користувача по email
  2. Перевірка чи акаунт не заблокований
  3. Перевірка пароля
  4. Якщо MFA увімкнено - створення тимчасової сесії в Redis
  5. Якщо MFA вимкнено - генерація JWT токенів
  6. Створення сесії в БД з IP адресою і User-Agent

POST /api/v1/auth/mfa/verify

Що робить: Перевіряє MFA код після логіну

Запит:

{
  "token": "123456",  // 6-значний TOTP код
  "temp_token": "temp_token_from_login"
}

Відповідь: Така ж як у /token без MFA


POST /api/v1/auth/refresh

Що робить: Оновлює access token використовуючи refresh token

Запит:

{
  "refresh_token": "eyJhbGciOiJSUzI1NiIs..."
}

Відповідь:

{
  "access_token": "new_access_token",
  "refresh_token": "new_refresh_token",
  "token_type": "Bearer",
  "expires_in": 900
}

Що відбувається всередині:

  1. Перевірка refresh token на валідність
  2. Додавання старого refresh token в Redis blacklist
  3. Генерація нової пари токенів

POST /api/v1/auth/logout

Що робить: Виходить з системи, анулює всі сесії

Запит: Потребує Authorization: Bearer {access_token} заголовок

Відповідь:

{
  "message": "Logged out successfully. 3 sessions revoked."
}

GET /api/v1/auth/userinfo

Що робить: Повертає базову інформацію про поточного користувача (OIDC endpoint)

Відповідь:

{
  "sub": "550e8400-e29b-41d4-a716-446655440000",
  "email": "user@example.com",
  "email_verified": true,
  "name": "John Doe",
  "preferred_username": "john_doe"
}

🔒 Multi-Factor Authentication (MFA)

POST /api/v1/auth/mfa/enable

Що робить: Починає процес увімкнення 2FA

Запит:

{
  "password": "SecurePass123!"  // Для підтвердження
}

Відповідь:

{
  "secret": "JBSWY3DPEHPK3PXP",  // Secret для TOTP
  "qr_code": "data:image/png;base64,iVBORw0KGgoAAAA...",  // QR код для Google Authenticator
  "backup_codes": [
    "1234-5678",
    "2345-6789",
    // ... ще 8 кодів
  ]
}

Важливо для фронтенду:

  • Покажіть QR код користувачу
  • Дайте можливість скопіювати secret вручну
  • Обов'язково покажіть backup коди і попросіть зберегти

POST /api/v1/auth/mfa/confirm

Що робить: Підтверджує увімкнення MFA

Запит:

{
  "token": "123456"  // Код з Google Authenticator
}

POST /api/v1/auth/mfa/disable

Що робить: Вимикає MFA

Запит:

{
  "password": "SecurePass123!",
  "token": "123456"  // Поточний MFA код
}

👤 Керування користувачами (/api/v1/users)

GET /api/v1/users/me

Що робить: Повертає повний профіль поточного користувача

Відповідь:

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "email": "user@example.com",
  "username": "john_doe",
  "full_name": "John Doe",
  "status": "active",
  "is_email_verified": true,
  "is_superuser": false,
  "mfa_enabled": true,
  "oauth_providers": {
    "google": true,
    "github": false
  },
  "metadata_json": {
    "preferences": {...},
    "settings": {...}
  },
  "created_at": "2024-01-15T10:30:00Z",
  "updated_at": "2024-01-15T14:20:00Z",
  "last_login_at": "2024-01-20T09:15:00Z"
}

PATCH /api/v1/users/me

Що робить: Оновлює дані профілю

Запит:

{
  "full_name": "John Smith",
  "username": "john_smith"
  // email не можна змінити без верифікації
}

GET /api/v1/users/me/sessions

Що робить: Показує всі активні сесії користувача

Відповідь:

[
  {
    "id": "session-id-1",
    "ip_address": "192.168.1.1",
    "user_agent": "Mozilla/5.0...",
    "device_info": {
      "browser": "Chrome",
      "os": "Windows 10"
    },
    "created_at": "2024-01-20T09:15:00Z",
    "last_activity": "2024-01-20T10:30:00Z",
    "expires_at": "2024-01-20T09:30:00Z"
  }
]

DELETE /api/v1/users/me/sessions/{session_id}

Що робить: Завершує конкретну сесію (наприклад, вихід на іншому пристрої)


🔑 Відновлення паролю

POST /api/v1/auth/password/reset

Що робить: Запит на скидання пароля

Запит:

{
  "email": "user@example.com"
}

Відповідь: Завжди успішна (щоб не розкривати чи існує email)

{
  "message": "If the email exists, a password reset link has been sent"
}

POST /api/v1/auth/password/reset/confirm

Що робить: Встановлює новий пароль

Запит:

{
  "token": "reset_token_from_email",
  "new_password": "NewSecurePass123!"
}

🔧 Як підключити фронтенд

1. Базова автентифікація

// Логін
const login = async (email, password) => {
  const formData = new FormData();
  formData.append('username', email);  // Так, це email!
  formData.append('password', password);
  formData.append('grant_type', 'password');
  
  const response = await fetch('http://localhost:8000/api/v1/auth/token', {
    method: 'POST',
    body: formData
  });
  
  const data = await response.json();
  
  if (data.token_type === 'mfa_required') {
    // Перенаправити на сторінку MFA
    localStorage.setItem('mfa_temp_token', data.access_token);
    return { requiresMFA: true };
  }
  
  // Зберегти токени
  localStorage.setItem('access_token', data.access_token);
  localStorage.setItem('refresh_token', data.refresh_token);
  
  return data;
};

// Використання токена в запитах
const fetchUserProfile = async () => {
  const token = localStorage.getItem('access_token');
  
  const response = await fetch('http://localhost:8000/api/v1/users/me', {
    headers: {
      'Authorization': `Bearer ${token}`
    }
  });
  
  if (response.status === 401) {
    // Токен протух, спробувати оновити
    await refreshToken();
    // Повторити запит
  }
  
  return response.json();
};

// Оновлення токена
const refreshToken = async () => {
  const refresh = localStorage.getItem('refresh_token');
  
  const response = await fetch('http://localhost:8000/api/v1/auth/refresh', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({ refresh_token: refresh })
  });
  
  const data = await response.json();
  localStorage.setItem('access_token', data.access_token);
  localStorage.setItem('refresh_token', data.refresh_token);
};

2. Інтеграція з іншими мікросервісами

Коли інші мікросервіси (Google Клас, Розклад, Месенджер) потребують інформацію про користувача:

# В іншому мікросервісі
async def get_current_user(token: str):
    # Варіант 1: Перевірити токен локально (потребує публічний ключ)
    response = await httpx.get("http://auth-service:8000/.well-known/jwks.json")
    public_key = response.json()["keys"][0]
    
    # Варіант 2: Запитати auth-service (простіше але повільніше)
    response = await httpx.get(
        "http://auth-service:8000/api/v1/auth/userinfo",
        headers={"Authorization": f"Bearer {token}"}
    )
    return response.json()

🛡 Безпека

Rate Limiting

  • 60 запитів на хвилину per IP
  • 1000 запитів на годину per IP
  • При перевищенні - HTTP 429

Password Policy

  • Мінімум 8 символів
  • Обов'язково: велика літера, мала літера, цифра
  • Блокування після 5 невдалих спроб на 30 хвилин

Token Security

  • Access token: 15 хвилин життя
  • Refresh token: 30 днів життя
  • Токени підписані RS256 (асиметричне шифрування)
  • Refresh token rotation (старі токени в blacklist)

🚀 Deployment

Docker Compose (для розробки)

# Запустити всі сервіси
docker-compose up -d

# Подивитися логи
docker-compose logs -f auth-service

# Зупинити
docker-compose down

Production (Kubernetes)

apiVersion: apps/v1
kind: Deployment
metadata:
  name: auth-service
spec:
  replicas: 3
  selector:
    matchLabels:
      app: auth-service
  template:
    metadata:
      labels:
        app: auth-service
    spec:
      containers:
      - name: auth-service
        image: your-registry/auth-service:latest
        ports:
        - containerPort: 8000
        env:
        - name: DATABASE_URL
          valueFrom:
            secretKeyRef:
              name: auth-secrets
              key: database-url
        livenessProbe:
          httpGet:
            path: /health
            port: 8000
          initialDelaySeconds: 30
          periodSeconds: 10

📊 Моніторинг

Метрики (Prometheus)

  • http_requests_total - кількість запитів
  • http_request_duration_seconds - час обробки
  • auth_token_generated_total - згенеровано токенів
  • auth_failed_login_total - невдалі спроби входу

Трейсинг (Jaeger)

Доступний на http://localhost:16686

  • Відстеження повного шляху запиту
  • Час виконання кожної операції
  • Візуалізація залежностей

Дашборди (Grafana)

Доступні на http://localhost:3001 (admin/admin)

  • Кількість активних користувачів
  • Швидкість відповіді API
  • Помилки автентифікації
  • Використання ресурсів

❓ FAQ для розробників

Q: Чому поле username при логіні, а не email? A: Це вимога OAuth2 специфікації. В полі username передаємо email.

Q: Як довго живуть токени? A: Access token - 15 хвилин, Refresh token - 30 днів. Налаштовується через змінні оточення.

Q: Чи можна використовувати без MFA? A: Так, MFA опціональне і вмикається користувачем за бажанням.

Q: Як інтегрувати з Google/GitHub OAuth? A: Додайте CLIENT_ID і CLIENT_SECRET в .env, сервіс автоматично активує OAuth ендпоінти.

Q: Що таке OpenFGA і навіщо воно? A: Це система для складних прав доступу. Наприклад, "користувач X може редагувати документ Y якщо він в групі Z". Для простих ролей (admin/user) достатньо JWT claims.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors