تطوير الويب

المصادقة مع better-auth في TanStack Start: جلسات وOAuth 2026

6 دقائق للقراءة

السلسلة: هذا الدرس جزء من سلسلة TanStack. اقرأ المقال الرئيسي.

وضع مصادقة صحيحة نادراً ما يكون الجزء المُسلّي. better-auth صارت في 2026 المكتبة المرجع في نظام TypeScript الحديث: framework-agnostic، جلسات آمنة افتراضياً، OAuth متعدد المزوّدين، magic links، 2FA، multi-tenant. مع TanStack Start وملحقها tanstackStartCookies، تتكامل في أقل من ساعة.

المتطلبات

  • Node.js 22 LTS، npm 10+ أو pnpm 9+
  • مشروع TanStack Start v1 شغّال مع TypeScript
  • قاعدة بيانات (PostgreSQL، Neon، Supabase، أو D1 على Cloudflare)
  • Drizzle ORM مثبَّت مع schema أوّلي

الخطوة 1 — تثبيت better-auth وتحضير Drizzle

npm install better-auth drizzle-orm
npm install pg
npm install -D drizzle-kit @types/pg
# .env
BETTER_AUTH_SECRET=$(openssl rand -base64 32)
BETTER_AUTH_URL=http://localhost:3000
DATABASE_URL=postgres://user:pass@localhost:5432/mydb

السرّ يجب أن يكون طويلاً وغير متوقَّع. في الإنتاج، السرّ في متغيّرات بيئة المضيف.

الخطوة 2 — إعداد better-auth مع Drizzle adapter

// src/lib/auth.ts
import { betterAuth } from 'better-auth'
import { drizzleAdapter } from 'better-auth/adapters/drizzle'
import { tanstackStartCookies } from 'better-auth/tanstack-start'
import { db } from '~/db/client'

export const auth = betterAuth({
  database: drizzleAdapter(db, { provider: 'pg' }),
  secret: process.env.BETTER_AUTH_SECRET,
  baseURL: process.env.BETTER_AUTH_URL,
  emailAndPassword: { enabled: true },
  plugins: [tanstackStartCookies()], // يجب أن يبقى آخراً
})

المعامل provider يدلّ على dialect SQL: pg لـ PostgreSQL، mysql، أو sqlite. لـ D1، استخدم sqlite مع client Drizzle مهيَّأ عبر drizzle-orm/d1.

الخطوة 3 — توليد جداول المصادقة

better-auth ينشئ آلياً أربعة جداول: user، session، account، verification. الـ schema يُولَّد عبر CLI:

npx @better-auth/cli generate --output ./src/db/auth-schema.ts
// src/db/schema.ts
export * from './auth-schema'
// ... جداولك الأخرى
npx drizzle-kit generate
npx drizzle-kit migrate

الخطوة 4 — كشف route catch-all /api/auth/$

better-auth يوفّر handler واحد يُجيب على كل routes /api/auth/*. نكشفه عبر route catch-all TanStack Router.

// src/routes/api/auth/$.ts
import { auth } from '~/lib/auth'
import { createFileRoute } from '@tanstack/react-router'

export const Route = createFileRoute('/api/auth/$')({
  server: {
    handlers: {
      GET: async ({ request }: { request: Request }) => auth.handler(request),
      POST: async ({ request }: { request: Request }) => auth.handler(request),
    },
  },
})

الخطوة 5 — إنشاء client React لـ signIn/signUp/signOut

// src/lib/auth-client.ts
import { createAuthClient } from 'better-auth/react'

export const authClient = createAuthClient({
  baseURL: import.meta.env.VITE_AUTH_URL,
})

export const { signIn, signUp, signOut, useSession } = authClient

hook useSession() يُرجع { data, isPending, error } ويُحدَّث آلياً بعد signIn أو signOut.

الخطوة 6 — بناء نموذج تسجيل

// src/routes/(auth)/signup.tsx
import { createFileRoute, useRouter } from '@tanstack/react-router'
import { useForm } from '@tanstack/react-form'
import { z } from 'zod'
import { signUp } from '~/lib/auth-client'

const schema = z.object({
  email: z.string().email(),
  password: z.string().min(8),
  name: z.string().min(2),
})

export const Route = createFileRoute('/(auth)/signup')({
  component: SignupPage,
})

function SignupPage() {
  const router = useRouter()
  const form = useForm({
    defaultValues: { email: '', password: '', name: '' },
    validators: { onChange: schema, onSubmit: schema },
    onSubmit: async ({ value }) => {
      const { error } = await signUp.email(value)
      if (error) throw new Error(error.message)
      router.navigate({ to: '/dashboard' })
    },
  })

  return (
    <form onSubmit={(e) => { e.preventDefault(); void form.handleSubmit() }}>
      {/* form.Field لـ email، password، name */}
    </form>
  )
}

signUp.email(value) يرسل البيانات إلى /api/auth/sign-up/email؛ better-auth ينشئ المستخدم، يُجزّئ كلمة السرّ بـ scrypt افتراضياً، يفتح جلسة، ويُرجع cookie.

الخطوة 7 — استرداد الجلسة جانب الخادم

// src/lib/auth-functions.ts
import { createServerFn } from '@tanstack/react-start'
import { getRequestHeaders } from '@tanstack/react-start/server'
import { auth } from './auth'

export const getSession = createServerFn({ method: 'GET' }).handler(async () => {
  const headers = getRequestHeaders()
  return await auth.api.getSession({ headers })
})

getRequestHeaders() يستردّ headers الطلب الحالي، منها cookie الجلسة. better-auth يتفحّصها، يتحقّق من التوقيع، يحمّل المستخدم من جدول session ويُرجع { user, session } أو null.

الخطوة 8 — حماية منطقة من التطبيق

// src/routes/_protected.tsx
import { createFileRoute, Outlet, redirect } from '@tanstack/react-router'
import { getSession } from '~/lib/auth-functions'

export const Route = createFileRoute('/_protected')({
  beforeLoad: async ({ location }) => {
    const session = await getSession()
    if (!session) {
      throw redirect({
        to: '/login',
        search: { redirect: location.href },
      })
    }
    return { user: session.user }
  },
  component: () => <Outlet />,
})

أي route تحت src/routes/_protected/ ترث هذه الحماية. return { user } يجعل المستخدم متاحاً في context الـ router.

الخطوة 9 — إضافة OAuth provider (GitHub)

لـ GitHub، أنشئ OAuth App في إعدادات حساب GitHub بـ URL callback http://localhost:3000/api/auth/callback/github. خذ Client ID وSecret في .env.

// src/lib/auth.ts (مقتطف مع GitHub)
import { betterAuth } from 'better-auth'
import { drizzleAdapter } from 'better-auth/adapters/drizzle'
import { tanstackStartCookies } from 'better-auth/tanstack-start'
import { db } from '~/db/client'

export const auth = betterAuth({
  database: drizzleAdapter(db, { provider: 'pg' }),
  secret: process.env.BETTER_AUTH_SECRET,
  baseURL: process.env.BETTER_AUTH_URL,
  emailAndPassword: { enabled: true },
  socialProviders: {
    github: {
      clientId: process.env.GITHUB_CLIENT_ID!,
      clientSecret: process.env.GITHUB_CLIENT_SECRET!,
    },
  },
  plugins: [tanstackStartCookies()],
})
import { signIn } from '~/lib/auth-client'

<button
  onClick={() => signIn.social({ provider: 'github', callbackURL: '/dashboard' })}
>
  Continuer avec GitHub
</button>

نفس الآلية تنطبق على Google، Apple، Discord، X، Microsoft، Facebook — better-auth يدعم 20+ مزوّداً في 2026.

الخطوة 10 — تسجيل خروج وتنظيف

import { signOut } from '~/lib/auth-client'
import { useRouter } from '@tanstack/react-router'

function LogoutButton() {
  const router = useRouter()
  return (
    <button
      onClick={async () => {
        await signOut()
        router.navigate({ to: '/' })
      }}
    >
      Se deconnecter
    </button>
  )
}

better-auth يدير أيضاً rate limiting لمحاولات الاتصال وانتهاء صلاحية الجلسات الانزلاقي بلا إعداد إضافي.

أخطاء شائعة

الخطأ السبب الحل
Cookie الجلسة مفقود ملحق tanstackStartCookies مفقود أضفه آخراً في plugins
Tables not found migrations Drizzle لم تُطبَّق أطلق drizzle-kit generate ثم migrate
OAuth callback 404 URL callback سيئ في المزوّد تحقّق BETTER_AUTH_URL وcallback في GitHub/Google
الجلسة لا تنجو من refresh cookie domain أو secure سيء في prod، استخدم HTTPS واضبط cookieOptions
خطأ CORS على OAuth baseURL لا يطابق Origin وَفّق BETTER_AUTH_URL مع URL التطبيق
signIn.social لا يُطلق إعادة توجيه Provider غير مُعلَن في socialProviders أضفه في auth.ts

أسئلة شائعة

هل server function لكل استدعاء auth؟ لا، client better-auth/react يكشفها (signIn، signUp، signOut، updateUser). احتفظ بـ server functions للأعمال التي تتطلب المستخدم الحالي.

كيف نُفعّل 2FA؟ أضف ملحق twoFactor() في plugins: [...]. Client يكشف authClient.twoFactor.enable() الذي يُرجع QR code.

هل يمكن تخصيص جدول user (role، orgId)؟ نعم، عبر user: { additionalFields: { role: { type: 'string' } } }. better-auth يضيف هذه الأعمدة عند التوليد التالي.

كيف ندير organisations multi-tenant؟ استخدم ملحق organization() الذي ينشئ جداول organization، member، invitation.

هل better-auth يشتغل على Cloudflare Workers؟ نعم، شريطة استخدام client Drizzle متوافق (D1 أو Hyperdrive لـ PostgreSQL).

مقالات ذات صلة

Sponsoriser ce contenu

Cet emplacement est à vous

Position premium en fin d'article — c'est l'instant où les lecteurs sont le plus engagés. Réservez cet espace pour votre marque, votre formation ou votre offre.

Recevoir nos tarifs
Publicité