Концепції, цінність і типові сценарії
Концепції, цінність і типові сценарії
Підготовка контенту платформи Ring
Підготовка контенту платформи Ring
Підготовка контенту платформи Ring
Повні шаблони реалізації аутентифікації з використанням Auth.js v5 на Ring Platform.
Див. документ Архітектура аутентифікації для огляду провайдерів та дизайнерських рішень. Повну довідку змінних середовища див. у Змінні середовища.
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. Детальніше див. Режими бекенду та бази даних.
Повні шаблони реалізації аутентифікації з використанням Auth.js v5 на Ring Platform.
Див. документ Архітектура аутентифікації для огляду провайдерів та дизайнерських рішень. Повну довідку змінних середовища див. у Змінні середовища.
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. Детальніше див. Режими бекенду та бази даних.
Повні шаблони реалізації аутентифікації з використанням Auth.js v5 на Ring Platform.
Див. документ Архітектура аутентифікації для огляду провайдерів та дизайнерських рішень. Повну довідку змінних середовища див. у Змінні середовища.
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. Детальніше див. Режими бекенду та бази даних.
Auth.js v5 автоматично зчитує AUTH_GOOGLE_ID, AUTH_GOOGLE_SECRET, AUTH_APPLE_ID, AUTH_APPLE_SECRET (Ring Mailer SMTP) зі змінних середовища — не потребує ручного налаштування clientId / clientSecret у конструкторі провайдера.
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
},
},
})
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,
}),
})
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()
}
import { auth } from "@/auth"
export default async function ProfilePage() {
const session = await auth()
if (!session) return <div>Будь ласка, увійдіть</div>
return <div>Вітаємо, {session.user.name}</div>
}
"use client"
import { useSession } from "next-auth/react"
export default function UserProfile() {
const { data: session, status } = useSession()
if (status === "loading") return <div>Завантаження...</div>
if (!session) return <div>Не аутентифіковано</div>
return <div>Користувач: {session.user.email}</div>
}
"use client"
import { SessionProvider } from "next-auth/react"
export function Providers({ children }: { children: React.ReactNode }) {
return <SessionProvider>{children}</SessionProvider>
}Auth.js v5 автоматично зчитує AUTH_GOOGLE_ID, AUTH_GOOGLE_SECRET, AUTH_APPLE_ID, AUTH_APPLE_SECRET (Ring Mailer SMTP) зі змінних середовища — не потребує ручного налаштування clientId / clientSecret у конструкторі провайдера.
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
},
},
})
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,
}),
})
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()
}
import { auth } from "@/auth"
export default async function ProfilePage() {
const session = await auth()
if (!session) return <div>Будь ласка, увійдіть</div>
return <div>Вітаємо, {session.user.name}</div>
}
"use client"
import { useSession } from "next-auth/react"
export default function UserProfile() {
const { data: session, status } = useSession()
if (status === "loading") return <div>Завантаження...</div>
if (!session) return <div>Не аутентифіковано</div>
return <div>Користувач: {session.user.email}</div>
}
"use client"
import { SessionProvider } from "next-auth/react"
export function Providers({ children }: { children: React.ReactNode }) {
return <SessionProvider>{children}</SessionProvider>
}Auth.js v5 автоматично зчитує AUTH_GOOGLE_ID, AUTH_GOOGLE_SECRET, AUTH_APPLE_ID, AUTH_APPLE_SECRET (Ring Mailer SMTP) зі змінних середовища — не потребує ручного налаштування clientId / clientSecret у конструкторі провайдера.
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
},
},
})
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,
}),
})
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()
}
import { auth } from "@/auth"
export default async function ProfilePage() {
const session = await auth()
if (!session) return <div>Будь ласка, увійдіть</div>
return <div>Вітаємо, {session.user.name}</div>
}
"use client"
import { useSession } from "next-auth/react"
export default function UserProfile() {
const { data: session, status } = useSession()
if (status === "loading") return <div>Завантаження...</div>
if (!session) return <div>Не аутентифіковано</div>
return <div>Користувач: {session.user.email}</div>
}
"use client"
import { SessionProvider } from "next-auth/react"
export function Providers({ children }: { children: React.ReactNode }) {
return <SessionProvider>{children}</SessionProvider>
}
# 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
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).*)"],
}
# 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
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).*)"],
}
# 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
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).*)"],
}