Concepts, value, and typical clone scenarios — less code.
Concepts, value, and typical clone scenarios — less code.
Підготовка контенту платформи Ring
Підготовка контенту платформи Ring
Підготовка контенту платформи Ring
Complete authentication implementation patterns using Auth.js v5 with Ring Platform.
Use Founder / Developer tabs in the docs sidebar to filter this page. See Authentication, Authentication Architecture, Ring Mailer, and Environment Variables.
Ring Platform supports these Auth.js v5 sign-in paths:
| Provider | What it enables |
|---|---|
| Google OAuth | Traditional OAuth redirect + Google One Tap (GIS with server-side JWT verification) |
| Telegram (web) | Login via Telegram OIDC (oauth.telegram.org) — same-tab redirect button on /login |
| Telegram Mini App | Silent session from Telegram.WebApp.initData via Credentials telegram-miniapp |
| Apple Sign-In | Native iOS/macOS sign-in via OAuth redirect |
| Ring Mailer | OTP, magic link (/verify#token=…), password — own SMTP or Ethereal. See Ring Mailer |
| Crypto Wallet | Nonce-signature verification via Viem (Ethereum, Polygon, Arbitrum, Optimism, Base) |
| Internal JWT | Machine-to-machine tokens for WebSocket and MCP gateway auth |
Session strategy: JWT (no server-side session store). 30-day max age, 24-hour update window.
The database adapter (PostgreSQL or Firebase) is selected automatically by DB_BACKEND_MODE. For details, see Backend Modes and Databases.
Prerequisite: shipped providers, BotFather checklist, Mini App, and FutureFeature backlog.
Deep-dive: file split, OIDC + Mini App modules, and adapters.
Next-step: telegram_stars invoices reuse the Mini App bot token helper.
Same-workflow: Apple-specific JWT / Services ID walkthrough.
Complete authentication implementation patterns using Auth.js v5 with Ring Platform.
Use Founder / Developer tabs in the docs sidebar to filter this page. See Authentication, Authentication Architecture, Ring Mailer, and Environment Variables.
Ring Platform supports these Auth.js v5 sign-in paths:
| Provider | What it enables |
|---|---|
| Google OAuth | Traditional OAuth redirect + Google One Tap (GIS with server-side JWT verification) |
| Telegram (web) | Login via Telegram OIDC (oauth.telegram.org) — same-tab redirect button on /login |
| Telegram Mini App | Silent session from Telegram.WebApp.initData via Credentials telegram-miniapp |
| Apple Sign-In | Native iOS/macOS sign-in via OAuth redirect |
| Ring Mailer | OTP, magic link (/verify#token=…), password — own SMTP or Ethereal. See Ring Mailer |
| Crypto Wallet | Nonce-signature verification via Viem (Ethereum, Polygon, Arbitrum, Optimism, Base) |
| Internal JWT | Machine-to-machine tokens for WebSocket and MCP gateway auth |
Session strategy: JWT (no server-side session store). 30-day max age, 24-hour update window.
The database adapter (PostgreSQL or Firebase) is selected automatically by DB_BACKEND_MODE. For details, see Backend Modes and Databases.
Prerequisite: shipped providers, BotFather checklist, Mini App, and FutureFeature backlog.
Deep-dive: file split, OIDC + Mini App modules, and adapters.
Next-step: telegram_stars invoices reuse the Mini App bot token helper.
Same-workflow: Apple-specific JWT / Services ID walkthrough.
Complete authentication implementation patterns using Auth.js v5 with Ring Platform.
Use Founder / Developer tabs in the docs sidebar to filter this page. See Authentication, Authentication Architecture, Ring Mailer, and Environment Variables.
Ring Platform supports these Auth.js v5 sign-in paths:
| Provider | What it enables |
|---|---|
| Google OAuth | Traditional OAuth redirect + Google One Tap (GIS with server-side JWT verification) |
| Telegram (web) | Login via Telegram OIDC (oauth.telegram.org) — same-tab redirect button on /login |
| Telegram Mini App | Silent session from Telegram.WebApp.initData via Credentials telegram-miniapp |
| Apple Sign-In | Native iOS/macOS sign-in via OAuth redirect |
| Ring Mailer | OTP, magic link (/verify#token=…), password — own SMTP or Ethereal. See Ring Mailer |
| Crypto Wallet | Nonce-signature verification via Viem (Ethereum, Polygon, Arbitrum, Optimism, Base) |
| Internal JWT | Machine-to-machine tokens for WebSocket and MCP gateway auth |
Session strategy: JWT (no server-side session store). 30-day max age, 24-hour update window.
The database adapter (PostgreSQL or Firebase) is selected automatically by DB_BACKEND_MODE. For details, see Backend Modes and Databases.
Prerequisite: shipped providers, BotFather checklist, Mini App, and FutureFeature backlog.
Deep-dive: file split, OIDC + Mini App modules, and adapters.
Next-step: telegram_stars invoices reuse the Mini App bot token helper.
Same-workflow: Apple-specific JWT / Services ID walkthrough.
k8s-postgres-fcm | PostgreSQLAdapter() |
firebase-full | FirestoreAdapter(adminDb) |
supabase-fcm | PostgreSQLAdapter() |
Ring wraps Auth.js with a tuned SessionProvider at features/auth/components/session-provider.tsx. Import this component — not SessionProvider directly from next-auth/react:
Canonical settings: refetchInterval={15 * 60}, refetchOnWindowFocus={false}, refetchWhenOffline={false}.
Auth.js v5 reads AUTH_GOOGLE_*, AUTH_TELEGRAM_*, and AUTH_APPLE_* for OAuth/OIDC. Mini App auth uses the bot API token via TELEGRAM_MINI_APP_BOT_TOKEN (not the OIDC client secret). Email auth uses Ring Mailer (SMTP_* / EMAIL_MODE) — not AUTH_RESEND_KEY.
Depends-on: full env reference for clone secrets.
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"
import {
isTelegramOidcConfigured,
TelegramOidcProvider,
} from "@/lib/auth/telegram-oidc"
import {
getTelegramMiniAppBotToken,
verifyTelegramMiniAppInitData,
isTelegramMiniAppAuthDateFresh,
} from "@/lib/auth/telegram-miniapp-initdata"
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: [
CredentialsProvider({ id: "email-otp", /* email + code */ }),
CredentialsProvider({ id: "email-magic", /* token */ }),
CredentialsProvider({ id: "credentials", /* email + password */ }),
GoogleProvider({
allowDangerousEmailAccountLinking: true,
checks: ["pkce", "state"],
}),
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 }
},
}),
AppleProvider({
allowDangerousEmailAccountLinking: true,
}),
...(isTelegramOidcConfigured()
? [TelegramOidcProvider({ allowDangerousEmailAccountLinking: true })]
: []),
CredentialsProvider({
id: "telegram-miniapp",
name: "Telegram Mini App",
credentials: { initData: { label: "Telegram initData", type: "text" } },
async authorize(credentials) {
const initData = String(credentials?.initData || "").trim()
const botToken = getTelegramMiniAppBotToken()
const parsed = verifyTelegramMiniAppInitData(initData, botToken)
if (!parsed?.user?.id || !isTelegramMiniAppAuthDateFresh(parsed.authDate)) {
return null
}
// resolveOrCreateTelegramUser(...) → return { id, email, name, image, role, telegramId }
},
}),
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
},
}),
],
})
"use client"
import { signIn } from "next-auth/react"
import { buildOAuthCallbackUrl } from "@/lib/auth/oauth-callback-url"
await signIn("telegram", { callbackUrl: buildOAuthCallbackUrl(from, locale) })
"use client"
import { signIn } from "next-auth/react"
const initData = window.Telegram?.WebApp?.initData
if (!initData) throw new Error("Not inside Telegram WebApp")
const result = await signIn("telegram-miniapp", {
initData,
redirect: false,
})
import { cert, initializeApp } from "firebase-admin/app"
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()
}k8s-postgres-fcm | PostgreSQLAdapter() |
firebase-full | FirestoreAdapter(adminDb) |
supabase-fcm | PostgreSQLAdapter() |
Ring wraps Auth.js with a tuned SessionProvider at features/auth/components/session-provider.tsx. Import this component — not SessionProvider directly from next-auth/react:
Canonical settings: refetchInterval={15 * 60}, refetchOnWindowFocus={false}, refetchWhenOffline={false}.
Auth.js v5 reads AUTH_GOOGLE_*, AUTH_TELEGRAM_*, and AUTH_APPLE_* for OAuth/OIDC. Mini App auth uses the bot API token via TELEGRAM_MINI_APP_BOT_TOKEN (not the OIDC client secret). Email auth uses Ring Mailer (SMTP_* / EMAIL_MODE) — not AUTH_RESEND_KEY.
Depends-on: full env reference for clone secrets.
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"
import {
isTelegramOidcConfigured,
TelegramOidcProvider,
} from "@/lib/auth/telegram-oidc"
import {
getTelegramMiniAppBotToken,
verifyTelegramMiniAppInitData,
isTelegramMiniAppAuthDateFresh,
} from "@/lib/auth/telegram-miniapp-initdata"
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: [
CredentialsProvider({ id: "email-otp", /* email + code */ }),
CredentialsProvider({ id: "email-magic", /* token */ }),
CredentialsProvider({ id: "credentials", /* email + password */ }),
GoogleProvider({
allowDangerousEmailAccountLinking: true,
checks: ["pkce", "state"],
}),
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 }
},
}),
AppleProvider({
allowDangerousEmailAccountLinking: true,
}),
...(isTelegramOidcConfigured()
? [TelegramOidcProvider({ allowDangerousEmailAccountLinking: true })]
: []),
CredentialsProvider({
id: "telegram-miniapp",
name: "Telegram Mini App",
credentials: { initData: { label: "Telegram initData", type: "text" } },
async authorize(credentials) {
const initData = String(credentials?.initData || "").trim()
const botToken = getTelegramMiniAppBotToken()
const parsed = verifyTelegramMiniAppInitData(initData, botToken)
if (!parsed?.user?.id || !isTelegramMiniAppAuthDateFresh(parsed.authDate)) {
return null
}
// resolveOrCreateTelegramUser(...) → return { id, email, name, image, role, telegramId }
},
}),
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
},
}),
],
})
"use client"
import { signIn } from "next-auth/react"
import { buildOAuthCallbackUrl } from "@/lib/auth/oauth-callback-url"
await signIn("telegram", { callbackUrl: buildOAuthCallbackUrl(from, locale) })
"use client"
import { signIn } from "next-auth/react"
const initData = window.Telegram?.WebApp?.initData
if (!initData) throw new Error("Not inside Telegram WebApp")
const result = await signIn("telegram-miniapp", {
initData,
redirect: false,
})
import { cert, initializeApp } from "firebase-admin/app"
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()
}k8s-postgres-fcm | PostgreSQLAdapter() |
firebase-full | FirestoreAdapter(adminDb) |
supabase-fcm | PostgreSQLAdapter() |
Ring wraps Auth.js with a tuned SessionProvider at features/auth/components/session-provider.tsx. Import this component — not SessionProvider directly from next-auth/react:
Canonical settings: refetchInterval={15 * 60}, refetchOnWindowFocus={false}, refetchWhenOffline={false}.
Auth.js v5 reads AUTH_GOOGLE_*, AUTH_TELEGRAM_*, and AUTH_APPLE_* for OAuth/OIDC. Mini App auth uses the bot API token via TELEGRAM_MINI_APP_BOT_TOKEN (not the OIDC client secret). Email auth uses Ring Mailer (SMTP_* / EMAIL_MODE) — not AUTH_RESEND_KEY.
Depends-on: full env reference for clone secrets.
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"
import {
isTelegramOidcConfigured,
TelegramOidcProvider,
} from "@/lib/auth/telegram-oidc"
import {
getTelegramMiniAppBotToken,
verifyTelegramMiniAppInitData,
isTelegramMiniAppAuthDateFresh,
} from "@/lib/auth/telegram-miniapp-initdata"
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: [
CredentialsProvider({ id: "email-otp", /* email + code */ }),
CredentialsProvider({ id: "email-magic", /* token */ }),
CredentialsProvider({ id: "credentials", /* email + password */ }),
GoogleProvider({
allowDangerousEmailAccountLinking: true,
checks: ["pkce", "state"],
}),
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 }
},
}),
AppleProvider({
allowDangerousEmailAccountLinking: true,
}),
...(isTelegramOidcConfigured()
? [TelegramOidcProvider({ allowDangerousEmailAccountLinking: true })]
: []),
CredentialsProvider({
id: "telegram-miniapp",
name: "Telegram Mini App",
credentials: { initData: { label: "Telegram initData", type: "text" } },
async authorize(credentials) {
const initData = String(credentials?.initData || "").trim()
const botToken = getTelegramMiniAppBotToken()
const parsed = verifyTelegramMiniAppInitData(initData, botToken)
if (!parsed?.user?.id || !isTelegramMiniAppAuthDateFresh(parsed.authDate)) {
return null
}
// resolveOrCreateTelegramUser(...) → return { id, email, name, image, role, telegramId }
},
}),
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
},
}),
],
})
"use client"
import { signIn } from "next-auth/react"
import { buildOAuthCallbackUrl } from "@/lib/auth/oauth-callback-url"
await signIn("telegram", { callbackUrl: buildOAuthCallbackUrl(from, locale) })
"use client"
import { signIn } from "next-auth/react"
const initData = window.Telegram?.WebApp?.initData
if (!initData) throw new Error("Not inside Telegram WebApp")
const result = await signIn("telegram-miniapp", {
initData,
redirect: false,
})
import { cert, initializeApp } from "firebase-admin/app"
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>Please sign in</div>
return <div>Welcome, {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>Loading...</div>
if (!session) return <div>Not authenticated</div>
return <div>User: {session.user.email}</div>
}
"use client"
import { SessionProvider } from "@/features/auth/components/session-provider"
export function AppClientShell({ children }: { children: React.ReactNode }) {
return <SessionProvider>{children}</SessionProvider>
}
AUTH_SECRET=your_auth_secret
AUTH_TRUST_HOST=true
AUTH_GOOGLE_ID=your_google_client_id
AUTH_GOOGLE_SECRET=your_google_client_secret
AUTH_TELEGRAM_ID=your_telegram_oidc_client_id
AUTH_TELEGRAM_SECRET=your_telegram_oidc_client_secret
# TELEGRAM_MINI_APP_BOT_TOKEN=... # Mini App initData HMAC
AUTH_APPLE_ID=your_apple_client_id
AUTH_APPLE_SECRET=your_apple_private_key
# EMAIL_MODE=ethereal
# SMTP_HOST= / SMTP_USER= / SMTP_PASSWORD= / SMTP_FROM=
# OTP_HMAC_SECRET=
import { auth } from "@/auth"
export default async function ProfilePage() {
const session = await auth()
if (!session) return <div>Please sign in</div>
return <div>Welcome, {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>Loading...</div>
if (!session) return <div>Not authenticated</div>
return <div>User: {session.user.email}</div>
}
"use client"
import { SessionProvider } from "@/features/auth/components/session-provider"
export function AppClientShell({ children }: { children: React.ReactNode }) {
return <SessionProvider>{children}</SessionProvider>
}
AUTH_SECRET=your_auth_secret
AUTH_TRUST_HOST=true
AUTH_GOOGLE_ID=your_google_client_id
AUTH_GOOGLE_SECRET=your_google_client_secret
AUTH_TELEGRAM_ID=your_telegram_oidc_client_id
AUTH_TELEGRAM_SECRET=your_telegram_oidc_client_secret
# TELEGRAM_MINI_APP_BOT_TOKEN=... # Mini App initData HMAC
AUTH_APPLE_ID=your_apple_client_id
AUTH_APPLE_SECRET=your_apple_private_key
# EMAIL_MODE=ethereal
# SMTP_HOST= / SMTP_USER= / SMTP_PASSWORD= / SMTP_FROM=
# OTP_HMAC_SECRET=
import { auth } from "@/auth"
export default async function ProfilePage() {
const session = await auth()
if (!session) return <div>Please sign in</div>
return <div>Welcome, {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>Loading...</div>
if (!session) return <div>Not authenticated</div>
return <div>User: {session.user.email}</div>
}
"use client"
import { SessionProvider } from "@/features/auth/components/session-provider"
export function AppClientShell({ children }: { children: React.ReactNode }) {
return <SessionProvider>{children}</SessionProvider>
}
AUTH_SECRET=your_auth_secret
AUTH_TRUST_HOST=true
AUTH_GOOGLE_ID=your_google_client_id
AUTH_GOOGLE_SECRET=your_google_client_secret
AUTH_TELEGRAM_ID=your_telegram_oidc_client_id
AUTH_TELEGRAM_SECRET=your_telegram_oidc_client_secret
# TELEGRAM_MINI_APP_BOT_TOKEN=... # Mini App initData HMAC
AUTH_APPLE_ID=your_apple_client_id
AUTH_APPLE_SECRET=your_apple_private_key
# EMAIL_MODE=ethereal
# SMTP_HOST= / SMTP_USER= / SMTP_PASSWORD= / SMTP_FROM=
# OTP_HMAC_SECRET=