Концепции, ценность и типичные сценарии клонирования — меньше кода.
Концепции, ценность и типичные сценарии клонирования — меньше кода.
Підготовка контенту платформи 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).*)"],
}