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

Повні шаблони реалізації аутентифікації з використанням 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).*)"],
}
```

## Пов'язана документація
