---
title: "Примеры аутентификации"
description: "Шаблоны многопровайдерной аутентификации Auth.js v5 — Google OAuth, Google One Tap, Apple Sign-In, магические ссылки через Ring Mailer и аутентификация через криптокошелек с адаптерами PostgreSQL или Firebase."
locale: "ru"
---
# Примеры аутентификации

Полные шаблоны реализации аутентификации с использованием Auth.js v5 на Ring Platform.

> **Info**
> См. документ [Архитектура аутентификации](/docs/en/architecture/authentication.md) для обзора провайдеров и дизайнерских решений. Полную справку переменных среды см. в [Переменные среды](/docs/en/deployment/environment.md).

### For founders

## Обзор провайдеров

Ring Platform поддерживает пять провайдеров аутентификации, все управляются через Auth.js v5:

| Провайдер | Что он дает |
|----------|----------------|
| **Google OAuth** | Традиционное OAuth перенаправление + Google One Tap (клиентское GIS окно с серверной JWT верификацией) |
| **Apple Sign-In** | Вход через OAuth перенаправление для iOS/macOS |
| **Ring Mailer** | Вход без пароля через email. Срок действия токена 24 часа, одноразовое использование. Требует SMTP_* или EMAIL_MODE=ethereal |
| **Crypto Wallet** | Верификация подписи через nonce с помощью Viem (Ethereum, Polygon, Arbitrum, Optimism, Base) |
| **Internal JWT** | Токены machine-to-machine для аутентификации WebSocket и MCP шлюза |

Стратегия сессии: **JWT** (без серверного хранилища сессий). Максимальный срок 30 дней, окно обновления 24 часа.

Адаптер базы данных (PostgreSQL или Firebase) выбирается автоматически по `DB_BACKEND_MODE`. Подробнее см. [Режимы бэкенда и базы данных](/docs/en/architecture/backend-modes-and-databases.md).

### For developers

## Конфигурация сервера Auth.js v5

```typescript title="auth.ts"
import NextAuth from "next-auth"
import { getAuthAdapter } from "@/lib/auth-adapter-singleton"
import authConfig from "./auth.config"
import GoogleProvider from "next-auth/providers/google"
import AppleProvider from "next-auth/providers/apple"
import CredentialsProvider from "next-auth/providers/credentials"

const authAdapter = getAuthAdapter()
const hasAdapter = !!authAdapter
export const { handlers, signIn, signOut, auth } = NextAuth({
  ...authConfig,
  ...(hasAdapter && { adapter: authAdapter }),
  session: {
    strategy: "jwt",
    maxAge: 30 * 24 * 60 * 60,
    updateAge: 24 * 60 * 60,
  },
  trustHost: true,
  providers: [
    // Ring Mailer: email-otp / email-magic Credentials — see EN /docs/features/ring-mailer

    // Google OAuth
    GoogleProvider({
      allowDangerousEmailAccountLinking: true,
      checks: ["pkce", "state"],
    }),

    // Google One Tap (client-side GIS popup)
    CredentialsProvider({
      id: "google-one-tap",
      name: "Google One Tap",
      credentials: { credential: { type: "text" } },
      async authorize(credentials) {
        if (!credentials?.credential) return null
        return { id: "gis-jwt-pending", email: credentials.credential as string }
      },
    }),

    // Apple Sign-In
    AppleProvider({
      allowDangerousEmailAccountLinking: true,
    }),

    // Crypto Wallet
    CredentialsProvider({
      id: "crypto-wallet",
      credentials: {
        walletAddress: { label: "Wallet Address", type: "text" },
        signedNonce: { label: "Signed Nonce", type: "text" },
      },
      async authorize(credentials) {
        if (!credentials?.walletAddress || !credentials?.signedNonce) return null
        // Nonce signature verification via Viem
        // Returns user object on success, null on failure
      },
    }),
  ],
  callbacks: {
    async jwt({ token, user, account, trigger }) {
      // Fetch fresh user data from database
      // Apply user row to JWT via applyUserRowToJwt()
      // Generate internal JWT for WebSocket auth
      return token
    },
    async session({ session, token }) {
      session.user.id = token.userId as string
      session.user.role = token.role as string
      session.user.isVerified = token.isVerified as boolean
      session.accessToken = token.accessToken as string
      return session
    },
    async signIn({ user, account, profile }) {
      // Google One Tap: verify JWT via google-auth-library
      // Check account status, block suspended users
      return true
    },
  },
})
```

## Стратегия учетных данных Firebase

Firebase Admin SDK сначала использует **Application Default Credentials (ADC)**. Резервный вариант `cert()` с явными учетными данными сервисного аккаунта используется только при наличии `AUTH_FIREBASE_CLIENT_EMAIL` и `AUTH_FIREBASE_PRIVATE_KEY`:

```typescript title="lib/firebase-admin.server.ts"
import { cert, initializeApp } from "firebase-admin/app"

// ADC-first (работает на Cloud Run, GKE, Cloud Functions без ручной настройки)
// cert() резервный вариант для локальной разработки / CI, когда установлены переменные среды:
adminApp = initializeApp({
  credential: cert({
    projectId: process.env.AUTH_FIREBASE_PROJECT_ID,
    clientEmail: process.env.AUTH_FIREBASE_CLIENT_EMAIL,
    privateKey: process.env.AUTH_FIREBASE_PRIVATE_KEY,
  }),
})
```

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

Адаптер определяется `DB_BACKEND_MODE` и инициализируется через синглтон:

```typescript title="lib/auth-adapter-singleton.ts"
import { FirestoreAdapter } from "@auth/firebase-adapter"
import { PostgreSQLAdapter } from "@/lib/auth/postgres-adapter"
import { shouldUseFirebaseForDatabase } from "@/lib/database/backend-mode-config"

export function getAuthAdapter() {
  if (shouldUseFirebaseForDatabase()) {
    const { getAdminDb } = require("@/lib/firebase-admin.server")
    return FirestoreAdapter(getAdminDb())
  }
  return PostgreSQLAdapter()
}
```

| `DB_BACKEND_MODE` | Адаптер |
|-------------------|---------|
| `k8s-postgres-fcm` | `PostgreSQLAdapter()` |
| `firebase-full` | `FirestoreAdapter(adminDb)` |
| `supabase-fcm` | `PostgreSQLAdapter()` |

## Серверная сессия

```typescript title="Server Component"
import { auth } from "@/auth"

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

## Клиентская сессия

```typescript title="Client Component"
"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}
}
```

## Настройка провайдера сессии

```typescript title="app/providers.tsx"
"use client"
import { SessionProvider } from "next-auth/react"

export function Providers({ children }: { children: React.ReactNode }) {
  return {children}
}
```

## Переменные среды

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

# Google OAuth (Auth.js auto-prefix)
AUTH_GOOGLE_ID=your_google_client_id
AUTH_GOOGLE_SECRET=your_google_client_secret

# Apple Sign-In (Auth.js auto-prefix)
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 Admin (для режима 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
```

> **Tip**
> Auth.js v5 автоматически читает `AUTH_GOOGLE_ID`, `AUTH_GOOGLE_SECRET`, `AUTH_APPLE_ID`, `AUTH_APPLE_SECRET`  (Ring Mailer SMTP) из переменных среды — не требует ручной настройки `clientId` / `clientSecret` в конструкторе провайдера.

## Защита маршрутов через middleware

```typescript title="middleware.ts"
import { auth } from "@/auth"
import { NextResponse } from "next/server"

export default auth((req) => {
  const { pathname } = req.nextUrl
  const session = req.auth

  if (pathname.startsWith("/auth") || pathname === "/") {
    return NextResponse.next()
  }
  if (!session && pathname.startsWith("/dashboard")) {
    return NextResponse.redirect(new URL("/login", req.url))
  }
  if (pathname.startsWith("/admin") && session?.user?.role !== "ADMIN") {
    return NextResponse.redirect(new URL("/403", req.url))
  }
  return NextResponse.next()
})

export const config = {
  matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],
}
```

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