---
title: "Архитектура Аутентификации"
description: "Auth.js v5 многопровайдерная аутентификация — Google OAuth, Apple Sign-In, Magic Links, Крипто-кошельки, с PostgreSQL или Firebase бэкендом."
locale: "ru"
---
# Архитектура Аутентификации

> **Info**
> Ring Platform использует **Auth.js v5** (NextAuth) с JWT стратегией сессий. Адаптер (PostgreSQL или Firebase) выбирается по `DB_BACKEND_MODE`.
>   Используйте вкладки **Основатель** / **Разработчик** в боковой панели документации для фильтрации по аудитории.

## Провайдеры

| Провайдер | Поток | Auth.js провайдер |
|-----------|------|-------------------|
| **Google OAuth** | Полный OAuth 2.0 redirect + Google Identity Services (GIS) One Tap | `GoogleProvider` + `CredentialsProvider("google-one-tap")` |
| **Apple Sign-In** | OAuth redirect, нативный iOS/macOS | `AppleProvider` |
| **Magic Links** | Email через Ring Mailer, 24-часовой срок действия токена | `Resend` |
| **Крипто-кошелек** | Nonce-подпись верификация (MetaMask, WalletConnect) | `CredentialsProvider("crypto-wallet")` |

Конфигурация находится в `auth.ts` и `auth.config.ts` в корне проекта. Auth.js v5 разделяет edge-безопасную конфигурацию (`auth.config.ts` — без провайдеров, минимальные callbacks) от полной серверной конфигурации (`auth.ts` — все провайдеры, адаптеры базы данных).

### For founders

## Как работает аутентификация

Auth.js v5 управляет всем потоком аутентификации — платформа не использует Firebase Auth напрямую. Firebase Admin SDK используется только для **серверной верификации токенов** и **поиска документов пользователей** в режиме `firebase-full`.

**Поток сессии:**
1. Пользователь входит через Google, Apple, email ссылку или крипто-кошелек
2. Auth.js v5 обрабатывает OAuth обмен токенами и верификацию
3. JWT сессия создается на сервере, хранится в HTTP-only cookie
4. PostgreSQL адаптер сохраняет записи пользователя/аккаунта/сессии когда `DB_BACKEND_MODE` основан на PostgreSQL
5. Firebase адаптер сохраняет в Firestore когда `DB_BACKEND_MODE=firebase-full`

**Ключевые дизайн-решения:**
- JWT стратегия (без хранения сессий в БД) для edge-совместимого развертывания
- 30-дневный максимальный возраст сессии с 24-часовым окном обновления
- Идентичность всегда является платформенным UUID (`users.id`), никогда Google `sub` или Apple `sub`
- Связывание аккаунтов через email (одинаковый email = тот же пользователь)

### For developers

## Структура файлов Auth.js v5

```
auth.config.ts          — Edge-совместимая конфигурация (пустые провайдеры, authorized callback, redirects)
auth.ts                 — Полная серверная конфигурация (все провайдеры, адаптер БД, JWT/сессионные callbacks)
lib/auth-adapter-singleton.ts  — Кешированный адаптер: PostgreSQLAdapter или FirestoreAdapter
lib/auth/postgres-adapter.ts   — Специализированный PostgreSQL адаптер для Auth.js v5
lib/firebase-admin.server.ts   — Firebase Admin SDK экземпляр (getAdminAuth, getAdminDb)
app/api/auth/[...nextauth]/route.ts  — Auth.js API route handler
```

## Конфигурация провайдеров

### Google OAuth (два режима)

**Традиционный OAuth** (redirect поток):

{`GoogleProvider({
  allowDangerousEmailAccountLinking: true,
  checks: ["pkce", "state"],
  wellKnown: "https://accounts.google.com/.well-known/openid-configuration",
})`}

**Google Identity Services (GIS) One Tap** (клиентское всплывающее окно):

{`CredentialsProvider({
  id: 'google-one-tap',
  name: 'Google One Tap',
  credentials: { credential: { type: 'text' } },
  async authorize(credentials) {
    // JWT верифицируется на сервере в signIn callback
    return { id: 'gis-jwt-pending', email: credentials.credential }
  },
})`}

GIS JWT верифицируется на сервере в `signIn` callback с помощью `google-auth-library`:

{`const ticket = await googleAuthClient.verifyIdToken({
  idToken: credential,
  audience: getGoogleIdTokenAudiences(),
})`}

### Apple Sign-In

{`AppleProvider({
  allowDangerousEmailAccountLinking: true,
})`}

### Magic Links (Resend)

Требует SMTP_* или EMAIL_MODE=ethereal переменную окружения и активный адаптер базы данных (для хранения токенов):

{`/* Ring Mailer Credentials — see auth.ts */`}

24-часовой срок действия токена (maxAge в Resend провайдере), одноразовое использование, автоматическая инвалидация.

### Крипто-кошелек

Nonce-подпись верификация через Viem. Поддерживает Ethereum, Polygon, Arbitrum, Optimism и Base:

{`CredentialsProvider({
  id: "crypto-wallet",
  credentials: {
    walletAddress: { label: "Wallet Address", type: "text" },
    signedNonce: { label: "Signed Nonce", type: "text" },
  },
  async authorize(credentials) {
    // 1. Поиск пользователя по адресу кошелька
    // 2. Верификация подписи nonce через verifyWalletNonceSignature()
    // 3. Очистка nonce, возврат объекта пользователя
    // 4. JWT callback создает сессию
  },
})`}

## Выбор адаптера

Адаптер определяется `DB_BACKEND_MODE`:

| Режим | Адаптер | Источник |
|-------|---------|----------|
| `k8s-postgres-fcm` | `PostgreSQLAdapter` | `lib/auth/postgres-adapter.ts` |
| `firebase-full` | `FirestoreAdapter` из `@auth/firebase-adapter` | через `getAdminDb()` |
| `supabase-fcm` | `PostgreSQLAdapter` | Тот же PostgreSQL путь |

{`export function getAuthAdapter() {
  const { shouldUseFirebaseForDatabase } = require('./database/backend-mode-config')
  const useFirebase = shouldUseFirebaseForDatabase()

  if (!useFirebase) {
    return PostgreSQLAdapter()
  }
  const { getAdminDb } = require("@/lib/firebase-admin.server")
  const adminDb = getAdminDb()
  return FirestoreAdapter(adminDb)
}`}

## Интеграция Firebase Admin SDK

Firebase Admin SDK используется в двух контекстах:
1. **Auth адаптер** (только `firebase-full` режим): `FirestoreAdapter` читает/записывает документы пользователей
2. **Крипто-кошелек аутентификация** (`firebase-full` режим): `getAdminDb().collection("users")` для поиска nonce

В режимах `k8s-postgres-fcm` и `supabase-fcm`, `getAdminDb()` возвращает **mock Firestore** — никакой реальной инициализации Firebase не происходит. FCM push уведомления все еще используют Firebase Admin через `firebase-admin.server.ts` (отдельно от auth пути).

## Использование на сервере

{`import { auth } from '@/auth'

export default async function ProfilePage() {
  const session = await auth()
  if (!session) return Пожалуйста, войдите
  return Добро пожаловать, {session.user.name}!
}`}

## Использование на клиенте

{`'use client'
import { useSession } from 'next-auth/react'

export default function UserProfile() {
  const { data: session, status } = useSession()
  if (status === 'loading') return Загрузка...
  if (!session) return Не аутентифицирован
  return Пользователь: {session.user.email}
}`}

## Переменные окружения

```bash
# Auth.js core
AUTH_SECRET=your_auth_secret
AUTH_TRUST_HOST=true

# Google OAuth
AUTH_GOOGLE_ID=your_google_client_id
AUTH_GOOGLE_SECRET=your_google_client_secret

# Apple Sign-In
AUTH_APPLE_ID=your_apple_client_id
AUTH_APPLE_SECRET=your_apple_private_key

# Magic Links
# Ring Mailer: EMAIL_MODE=ethereal or SMTP_HOST / SMTP_USER / SMTP_PASSWORD
# OTP_HMAC_SECRET=

# Firebase (только для firebase-full режима)
AUTH_FIREBASE_PROJECT_ID=your_firebase_project_id
AUTH_FIREBASE_CLIENT_EMAIL=your_firebase_client_email
AUTH_FIREBASE_PRIVATE_KEY=your_firebase_private_key

# WalletConnect
NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID=your_project_id
```

## Связанная документация
