---
title: "Authentication Architecture"
description: "Auth.js v5 multi-provider authentication — Google, Telegram OIDC + Mini App initData, Apple, Ring Mailer SMTP, crypto wallets, with PostgreSQL or Firebase backends."
locale: "en"
---
# Authentication Architecture

> **Info**
> Ring Platform uses **Auth.js v5** (NextAuth) with a JWT session strategy. The adapter (PostgreSQL or Firebase) is selected by `DB_BACKEND_MODE`.
>   Use **Founder** / **Developer** tabs in the docs sidebar to filter by audience.

## Providers

| Provider | Flow | Auth.js provider |
|----------|------|-----------------|
| **Google OAuth** | Full OAuth 2.0 redirect + Google Identity Services (GIS) One Tap | `GoogleProvider` + `CredentialsProvider("google-one-tap")` |
| **Telegram (web)** | OIDC Authorization Code + PKCE (`oauth.telegram.org`) | Custom `TelegramOidcProvider` (`id: "telegram"`) when env set |
| **Telegram Mini App** | WebApp `initData` HMAC (`WebAppData` secret) | `CredentialsProvider("telegram-miniapp")` |
| **Apple Sign-In** | OAuth redirect, native iOS/macOS | `AppleProvider` |
| **Ring Mailer** | OTP + magic link / verify / reset via own SMTP (`lib/mailer.ts`) | Credentials `email-otp` / `email-magic` / `credentials` |
| **Crypto Wallet** | Nonce-signature verification (MetaMask, WalletConnect) | `CredentialsProvider("crypto-wallet")` |

Configuration lives in `auth.ts` and `auth.config.ts` at the project root. Auth.js v5 splits edge-safe config (`auth.config.ts` — no providers, minimal callbacks) from full server config (`auth.ts` — all providers, database adapters).

Product overview and BotFather checklist: [Authentication](/docs/features/authentication.md).

### For founders

## How authentication works

Auth.js v5 manages the entire authentication flow — the platform does not use Firebase Auth directly. Firebase Admin SDK is used only for **server-side token verification** and **user document lookups** in `firebase-full` mode.

**Session flow:**
1. User signs in via Google, Telegram (web OIDC or Mini App initData), Apple, Ring Mailer (OTP / magic link / password), or crypto wallet
2. Auth.js v5 handles OAuth/OIDC exchange or Credentials authorize
3. JWT session is created on the server, stored in an HTTP-only cookie
4. PostgreSQL adapter persists user/account/session records when `DB_BACKEND_MODE` is PostgreSQL-based
5. Firebase adapter persists to Firestore when `DB_BACKEND_MODE=firebase-full`

**Key design decisions:**
- JWT strategy (no database session store) for edge-compatible deployment
- 30-day session max age with 24-hour update window
- Identity is always a platform UUID (`users.id`), never Google `sub`, Apple `sub`, or Telegram id alone as the session primary key
- Email-based account linking across providers when email exists; Telegram can create users without email
- Telegram Login (OIDC), Mini App initData, Login Widget linking, and the [admin Telegram bot](/docs/features/manage-via-telegram.md) use **different** secrets / crypto — see the surface table on [Authentication](/docs/features/authentication.md)

### For developers

## Auth.js v5 file structure

```
auth.config.ts          — Edge-compatible config (empty providers, authorized callback, redirects)
auth.ts                 — Full server config (all providers, database adapter, JWT/session callbacks)
lib/auth-adapter-singleton.ts  — Cached adapter: PostgreSQLAdapter or FirestoreAdapter
lib/auth/postgres-adapter.ts   — Custom PostgreSQL adapter for Auth.js v5
lib/auth/telegram-oidc.ts      — Telegram OIDC provider + claim helpers
lib/auth/telegram-miniapp-initdata.ts — Mini App WebAppData HMAC + getTelegramMiniAppBotToken
lib/auth/telegram-login-widget-hash.ts — Legacy Login Widget HMAC
lib/firebase-admin.server.ts   — Firebase Admin SDK instance (getAdminAuth, getAdminDb)
app/api/auth/[...nextauth]/route.ts  — Auth.js API route handler
app/api/auth/telegram/callback/route.ts — Session-required profile linking (widget)
```

## Provider configuration

### Google OAuth (dual mode)

**Traditional OAuth** (redirect flow):

{`GoogleProvider({
  allowDangerousEmailAccountLinking: true,
  checks: ["pkce", "state"],
  wellKnown: "https://accounts.google.com/.well-known/openid-configuration",
})`}

**Google Identity Services (GIS) One Tap** (client-side popup):

{`CredentialsProvider({
  id: 'google-one-tap',
  name: 'Google One Tap',
  credentials: { credential: { type: 'text' } },
  async authorize(credentials) {
    // JWT verified server-side in signIn callback
    return { id: 'gis-jwt-pending', email: credentials.credential }
  },
})`}

The GIS JWT is verified server-side in the `signIn` callback using `google-auth-library`:

{`const ticket = await googleAuthClient.verifyIdToken({
  idToken: credential,
  audience: getGoogleIdTokenAudiences(),
})`}

### Telegram OIDC

Registered only when `AUTH_TELEGRAM_ID` and `AUTH_TELEGRAM_SECRET` are set:

{`...(isTelegramOidcConfigured()
  ? [TelegramOidcProvider({ allowDangerousEmailAccountLinking: true })]
  : [])`}

- Discovery: `https://oauth.telegram.org/.well-known/openid-configuration`
- Soft-launch scopes: `openid profile`
- Token auth method: `client_secret_basic`
- Checks: `pkce`, `state`
- Profile from **id_token** claims (Telegram has no UserInfo endpoint)
- Resolve: `resolveOrCreateTelegramUser` in `features/auth/services/user-resolve.ts`
- Callback URI: `{origin}/api/auth/callback/telegram` (Auth.js) — do not confuse with `/api/auth/telegram/callback` (widget linking)

### Telegram Mini App initData

Always registered as Credentials `id: "telegram-miniapp"` in `auth.ts`. Authorize:

1. `getTelegramMiniAppBotToken()` — prefer `TELEGRAM_MINI_APP_BOT_TOKEN`, then `TELEGRAM_BOT_TOKEN`, `ADMIN_BOT_TOKEN`, `TELEGRAM_LOGIN_BOT_TOKEN`, `N9LIFE_BOT_TOKEN`
2. `verifyTelegramMiniAppInitData(initData, botToken)` — secret key material is HMAC with key **`WebAppData`** (not `SHA256(bot_token)`)
3. `isTelegramMiniAppAuthDateFresh` — default max age **86400** seconds
4. `resolveOrCreateTelegramUser` with Telegram id / name / username / photo from parsed `user` JSON

Client shape: `signIn('telegram-miniapp', { initData, redirect: false })`. Tests: `__tests__/auth/telegram-miniapp-initdata.test.ts`.

### Apple Sign-In

{`AppleProvider({
  allowDangerousEmailAccountLinking: true,
})`}

### Ring Mailer (OTP / magic link)

Own SMTP via `lib/mailer.ts`. Tokens live in Postgres `email_login_tokens` (migration `038`). Magic links use hash URLs (`/verify#token=…`) and are consumed only in Credentials `authorize` — never on GET.

{`CredentialsProvider({ id: 'email-otp', /* email + code */ })
CredentialsProvider({ id: 'email-magic', /* token */ })
CredentialsProvider({ id: 'credentials', /* email + password */ })`}

Server Actions: `app/_actions/auth-email-actions.ts`. Full setup: [Ring Mailer](/docs/features/ring-mailer.md).

### Crypto Wallet

Nonce-based signature verification via Viem. Supports Ethereum, Polygon, Arbitrum, Optimism, and Base:

{`CredentialsProvider({
  id: "crypto-wallet",
  credentials: {
    walletAddress: { label: "Wallet Address", type: "text" },
    signedNonce: { label: "Signed Nonce", type: "text" },
  },
  async authorize(credentials) {
    // 1. Look up user by wallet address
    // 2. Verify nonce signature via verifyWalletNonceSignature()
    // 3. Clear nonce, return user object
    // 4. JWT callback creates session
  },
})`}

## Adapter selection

The adapter is determined by `DB_BACKEND_MODE`:

| Mode | Adapter | Source |
|------|---------|--------|
| `k8s-postgres-fcm` | `PostgreSQLAdapter` | `lib/auth/postgres-adapter.ts` |
| `firebase-full` | `FirestoreAdapter` from `@auth/firebase-adapter` | via `getAdminDb()` |
| `supabase-fcm` | `PostgreSQLAdapter` | Same PostgreSQL path |

{`export function getAuthAdapter() {
  const { shouldUseFirebaseForDatabase } = require('./database/backend-mode-config')
  const useFirebase = shouldUseFirebaseForDatabase()

  if (!useFirebase) {
    return PostgreSQLAdapter()
  }
  const { getAdminDb } = require("@/lib/firebase-admin.server")
  const adminDb = getAdminDb()
  return FirestoreAdapter(adminDb)
}`}

## Firebase Admin SDK integration

Firebase Admin SDK is used in two contexts:
1. **Auth adapter** (`firebase-full` mode only): `FirestoreAdapter` reads/writes user documents
2. **Crypto wallet auth**: `db().readDoc('users', storageId)` for nonce lookup (BackendSelector routes Firebase or PostgreSQL)

In `k8s-postgres-fcm` and `supabase-fcm` modes, `getAdminDb()` returns a **mock Firestore** — no real Firebase init happens. FCM push messaging still uses Firebase Admin through `firebase-admin.server.ts` (separate from the auth path).

## Server-side usage

{`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 usage

{`'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}
}`}

## Environment variables

```bash
# Auth.js core
AUTH_SECRET=your_auth_secret
AUTH_TRUST_HOST=true

# Google OAuth
AUTH_GOOGLE_ID=your_google_client_id
AUTH_GOOGLE_SECRET=your_google_client_secret

# Telegram Web Login OIDC (BotFather → Web Login)
AUTH_TELEGRAM_ID=your_telegram_oidc_client_id
AUTH_TELEGRAM_SECRET=your_telegram_oidc_client_secret
# ADMIN_BOT_TOKEN=...   # Login Widget hash / admin bot API
# TELEGRAM_MINI_APP_BOT_TOKEN=...  # Mini App initData HMAC (+ Stars invoices)

# Apple Sign-In
AUTH_APPLE_ID=your_apple_client_id
AUTH_APPLE_SECRET=your_apple_private_key

# Ring Mailer (no AUTH_RESEND_*)
# EMAIL_MODE=ethereal
# SMTP_HOST= / SMTP_USER= / SMTP_PASSWORD= / SMTP_FROM=
# OTP_HMAC_SECRET=

# Firebase (for firebase-full mode only)
AUTH_FIREBASE_PROJECT_ID=your_firebase_project_id
AUTH_FIREBASE_CLIENT_EMAIL=your_firebase_client_email
AUTH_FIREBASE_PRIVATE_KEY=your_firebase_private_key

# WalletConnect
NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID=your_project_id
```

## Related documentation

  
- [features/authentication](/docs/features/authentication.md) — Prerequisite: product overview, BotFather checklist, Mini App, and Telegram surface boundary.

  
- [examples/authentication](/docs/examples/authentication.md) — Next-step: integrator snippets for OIDC and telegram-miniapp Credentials.

  
- [architecture/backend-modes-and-databases](/docs/architecture/backend-modes-and-databases.md) — Depends-on: how DB_BACKEND_MODE picks the Auth.js adapter.

  
- [deployment/environment](/docs/deployment/environment.md) — Same-workflow: complete AUTH_* and SMTP env blocks for clones.

  
- [features/manage-via-telegram](/docs/features/manage-via-telegram.md) — See-also: admin bot whitelist — not member OIDC or Mini App Login.

  
- [features/subscriptions](/docs/features/subscriptions.md) — See-also: telegram_stars reuses getTelegramMiniAppBotToken for XTR invoices.
