---
title: "Authentication Examples"
description: "Auth.js v5 multi-provider patterns — Google, Telegram OIDC + Mini App initData, Apple, Ring Mailer SMTP, and crypto wallet auth with PostgreSQL or Firebase adapters."
locale: "en"
---
# Authentication Examples

Complete authentication implementation patterns using Auth.js v5 with Ring Platform.

> **Info**
> Use **Founder** / **Developer** tabs in the docs sidebar to filter this page. See [Authentication](/docs/features/authentication.md), [Authentication Architecture](/docs/architecture/authentication.md), [Ring Mailer](/docs/features/ring-mailer.md), and [Environment Variables](/docs/deployment/environment.md).

### For founders

## Provider overview

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](/docs/features/ring-mailer.md) |
| **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](/docs/architecture/backend-modes-and-databases.md).

### For developers

## Auth.js v5 server configuration

Canonical providers live in root `auth.ts`. Shape (illustrative — prefer reading the file):

```typescript title="auth.ts (illustrative)"
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
      },
    }),
  ],
})
```

Request OTP / magic link via `app/_actions/auth-email-actions.ts`, then `signIn('email-otp')` or `signIn('email-magic')`. Do **not** import `next-auth/providers/resend`.

## Telegram sign-in (client) — web OIDC

Prefer the shipped button (locale + `buildOAuthCallbackUrl`):

```typescript title="features/auth/components/telegram-signin-button.tsx (shape)"
"use client"
import { signIn } from "next-auth/react"
import { buildOAuthCallbackUrl } from "@/lib/auth/oauth-callback-url"

await signIn("telegram", { callbackUrl: buildOAuthCallbackUrl(from, locale) })
```

Requires `AUTH_TELEGRAM_ID` / `AUTH_TELEGRAM_SECRET`. BotFather Allowed URL must include `{origin}/api/auth/callback/telegram`.

## Telegram Mini App sign-in (client)

Call from a WebApp page that has loaded Telegram’s script (white-label shells may add `/tg-mini-app`; platform ships auth only):

```typescript title="Mini App client (shape)"
"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,
})
```

Requires a bot token reachable via `getTelegramMiniAppBotToken()` (`TELEGRAM_MINI_APP_BOT_TOKEN` preferred).

## Firebase credential strategy

Firebase Admin SDK uses **Application Default Credentials (ADC)** first. The `cert()` fallback with explicit service-account credentials is only used when `AUTH_FIREBASE_CLIENT_EMAIL` and `AUTH_FIREBASE_PRIVATE_KEY` are present:

```typescript title="lib/firebase-admin.server.ts"
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,
  }),
})
```

## Adapter selection

```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` | Adapter |
|-------------------|---------|
| `k8s-postgres-fcm` | `PostgreSQLAdapter()` |
| `firebase-full` | `FirestoreAdapter(adminDb)` |
| `supabase-fcm` | `PostgreSQLAdapter()` |

## Server-side session

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

export default async function ProfilePage() {
  const session = await auth()
  if (!session) return Please sign in
  return Welcome, {session.user.name}
}
```

## Client-side session

```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 Loading...
  if (!session) return Not authenticated
  return User: {session.user.email}
}
```

## Session provider setup

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`:

```typescript title="components/providers/app-client-shell.tsx"
"use client"
import { SessionProvider } from "@/features/auth/components/session-provider"

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

Canonical settings: `refetchInterval={15 * 60}`, `refetchOnWindowFocus={false}`, `refetchWhenOffline={false}`.

## Environment variables

```bash
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=
```

> **Tip**
> 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`.

## Related documentation

  
- [features/authentication](/docs/features/authentication.md) — Prerequisite: shipped providers, BotFather checklist, Mini App, and FutureFeature backlog.

  
- [architecture/authentication](/docs/architecture/authentication.md) — Deep-dive: file split, OIDC + Mini App modules, and adapters.

  
- [features/subscriptions](/docs/features/subscriptions.md) — Next-step: telegram_stars invoices reuse the Mini App bot token helper.

  
- [examples/apple-signin-integration](/docs/examples/apple-signin-integration.md) — Same-workflow: Apple-specific JWT / Services ID walkthrough.

  
- [deployment/environment](/docs/deployment/environment.md) — Depends-on: full env reference for clone secrets.
