Better Auth مع Next.js 15: الدليل الشامل للمصادقة في 2026

المصادقة كما يجب أن تكون. Better Auth هي مكتبة المصادقة المستقلة عن الأطر البرمجية والمبنية على TypeScript التي اجتاحت عالم التطوير في 2026. تتعامل مع الجلسات وOAuth والتحقق من البريد الإلكتروني والمصادقة الثنائية والمزيد — بدون أي قيود أو تبعيات مع أمان كامل للأنواع. في هذا الدليل ستبني نظام مصادقة متكاملاً مع Next.js 15.
ما ستتعلمه
بنهاية هذا الدليل، ستتمكن من:
- إعداد Better Auth في مشروع Next.js 15 مع App Router
- تنفيذ تسجيل الدخول بالبريد الإلكتروني وكلمة المرور
- إضافة مزودي GitHub وGoogle OAuth
- حماية المسارات باستخدام middleware والتحقق من الجلسات على الخادم
- بناء نظام التحكم بالوصول المبني على الأدوار (RBAC)
- إدارة التحقق من البريد الإلكتروني وإعادة تعيين كلمة المرور
- إدارة جلسات المستخدمين بأمان باستخدام ملفات تعريف الارتباط
- نشر نظام مصادقة جاهز للإنتاج
المتطلبات الأساسية
قبل البدء، تأكد من توفر:
- Node.js 20+ مثبت (
node --version) - خبرة في TypeScript (الأنواع، async/await)
- معرفة بـ Next.js 15 (App Router، Server Components، Server Actions)
- PostgreSQL يعمل محلياً أو قاعدة بيانات سحابية (Neon أو Supabase أو ما شابه)
- محرر أكواد — يُنصح بـ VS Code أو Cursor
- تطبيقات GitHub وGoogle OAuth مُنشأة (سنشرح ذلك)
لماذا Better Auth؟
تطور مشهد المصادقة في JavaScript بشكل كبير. تم إيقاف Lucia Auth، ولدى NextAuth (Auth.js) تعقيداتها، وأراد كثير من المطورين شيئاً أبسط وأقوى. Better Auth تملأ هذه الفجوة:
| الميزة | Better Auth | Auth.js v5 | JWT مخصص |
|---|---|---|---|
| أمان الأنواع | استنتاج TypeScript كامل | جزئي | يدوي |
| التبعية للإطار | لا شيء | مركزة على Next.js | لا شيء |
| التحكم بقاعدة البيانات | أنت تملكها | معتمد على المحولات | أنت تملكها |
| مزودو OAuth | 20+ مدمج | 80+ مدمج | يدوي |
| المصادقة الثنائية | إضافة مدمجة | مجتمعية | يدوي |
| RBAC | إضافة مدمجة | يدوي | يدوي |
| التحقق من البريد | مدمج | مدمج | يدوي |
| استراتيجية الجلسة | Cookie + DB | JWT أو DB | JWT |
| حجم الحزمة | ~15KB | ~30KB | ~2KB |
| منحنى التعلم | منخفض | متوسط | مرتفع |
تمنحك Better Auth تحكماً كاملاً في قاعدة بياناتك، بدون أي تبعية لمزود خدمة، مع بنية إضافات تتيح لك إضافة الميزات تدريجياً.
الخطوة 1: إنشاء مشروع Next.js
ابدأ بإنشاء تطبيق Next.js 15 جديد:
npx create-next-app@latest better-auth-demo --typescript --tailwind --eslint --app --src-dir --import-alias "@/*"
cd better-auth-demoثبّت Better Auth والتبعيات المطلوبة:
npm install better-auth
npm install -D @types/better-sqlite3سنستخدم PostgreSQL مع Drizzle ORM. ثبّت تبعيات قاعدة البيانات:
npm install drizzle-orm @neondatabase/serverless
npm install -D drizzle-kitالخطوة 2: إعداد قاعدة البيانات
أنشئ ملف .env.local في جذر المشروع:
DATABASE_URL="postgresql://user:password@localhost:5432/better_auth_demo"
BETTER_AUTH_SECRET="مفتاحك-السري-يجب-أن-يكون-32-حرفاً-على-الأقل"
BETTER_AUTH_URL="http://localhost:3000"
GITHUB_CLIENT_ID="معرف-عميل-github"
GITHUB_CLIENT_SECRET="سر-عميل-github"
GOOGLE_CLIENT_ID="معرف-عميل-google"
GOOGLE_CLIENT_SECRET="سر-عميل-google"أنشئ مفتاحاً سرياً آمناً:
openssl rand -base64 32أنشئ إعداد قاعدة البيانات في src/db/index.ts:
import { neon } from "@neondatabase/serverless";
import { drizzle } from "drizzle-orm/neon-http";
const sql = neon(process.env.DATABASE_URL!);
export const db = drizzle(sql);الخطوة 3: إعداد خادم Better Auth
أنشئ ملف الإعداد الأساسي في src/lib/auth.ts:
import { betterAuth } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { db } from "@/db";
export const auth = betterAuth({
database: drizzleAdapter(db, {
provider: "pg",
}),
emailAndPassword: {
enabled: true,
requireEmailVerification: false, // فعّلها في الإنتاج
},
socialProviders: {
github: {
clientId: process.env.GITHUB_CLIENT_ID!,
clientSecret: process.env.GITHUB_CLIENT_SECRET!,
},
google: {
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
},
},
session: {
expiresIn: 60 * 60 * 24 * 7, // 7 أيام
updateAge: 60 * 60 * 24, // تحديث الجلسة كل 24 ساعة
cookieCache: {
enabled: true,
maxAge: 5 * 60, // تخزين مؤقت لمدة 5 دقائق
},
},
});الخطوة 4: إنشاء معالج مسار API
يحتاج Better Auth إلى مسار API للتعامل مع جميع طلبات المصادقة. أنشئ src/app/api/auth/[...all]/route.ts:
import { auth } from "@/lib/auth";
import { toNextJsHandler } from "better-auth/next-js";
export const { GET, POST } = toNextJsHandler(auth);هذا كل شيء — يتعامل Better Auth الآن تلقائياً مع جميع المسارات تحت /api/auth/*، بما في ذلك:
POST /api/auth/sign-up/email— التسجيل بالبريد وكلمة المرورPOST /api/auth/sign-in/email— تسجيل الدخولGET /api/auth/sign-in/social— إعادة توجيه OAuthPOST /api/auth/sign-out— تسجيل الخروجGET /api/auth/session— الحصول على الجلسة الحالية
الخطوة 5: إنشاء جداول قاعدة البيانات
يمكن لـ Better Auth إنشاء مخطط قاعدة البيانات تلقائياً. نفّذ:
npx better-auth generateهذا ينشئ ملف ترحيل يحتوي على الجداول المطلوبة: user وsession وaccount وverification. طبّق الترحيل:
npx drizzle-kit pushالمخطط المُنشأ يتضمن:
CREATE TABLE "user" (
"id" TEXT PRIMARY KEY,
"name" TEXT NOT NULL,
"email" TEXT UNIQUE NOT NULL,
"emailVerified" BOOLEAN DEFAULT FALSE,
"image" TEXT,
"createdAt" TIMESTAMP DEFAULT NOW(),
"updatedAt" TIMESTAMP DEFAULT NOW()
);
CREATE TABLE "session" (
"id" TEXT PRIMARY KEY,
"userId" TEXT NOT NULL REFERENCES "user"("id"),
"token" TEXT UNIQUE NOT NULL,
"expiresAt" TIMESTAMP NOT NULL,
"ipAddress" TEXT,
"userAgent" TEXT
);
CREATE TABLE "account" (
"id" TEXT PRIMARY KEY,
"userId" TEXT NOT NULL REFERENCES "user"("id"),
"accountId" TEXT NOT NULL,
"providerId" TEXT NOT NULL,
"accessToken" TEXT,
"refreshToken" TEXT,
"expiresAt" TIMESTAMP,
"password" TEXT
);
CREATE TABLE "verification" (
"id" TEXT PRIMARY KEY,
"identifier" TEXT NOT NULL,
"value" TEXT NOT NULL,
"expiresAt" TIMESTAMP NOT NULL
);الخطوة 6: إنشاء عميل المصادقة
أنشئ مساعد المصادقة من جهة العميل في src/lib/auth-client.ts:
import { createAuthClient } from "better-auth/react";
export const authClient = createAuthClient({
baseURL: process.env.NEXT_PUBLIC_APP_URL || "http://localhost:3000",
});
export const {
signIn,
signUp,
signOut,
useSession,
} = authClient;أضف NEXT_PUBLIC_APP_URL إلى ملف .env.local:
NEXT_PUBLIC_APP_URL="http://localhost:3000"الخطوة 7: بناء صفحة التسجيل
أنشئ نموذج التسجيل في src/app/auth/sign-up/page.tsx:
"use client";
import { useState } from "react";
import { signUp } from "@/lib/auth-client";
import { useRouter } from "next/navigation";
import Link from "next/link";
export default function SignUpPage() {
const [name, setName] = useState("");
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [error, setError] = useState("");
const [loading, setLoading] = useState(false);
const router = useRouter();
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setError("");
setLoading(true);
const { error } = await signUp.email({
email,
password,
name,
});
if (error) {
setError(error.message || "حدث خطأ ما");
setLoading(false);
return;
}
router.push("/dashboard");
};
return (
<div className="min-h-screen flex items-center justify-center bg-gray-50">
<div className="max-w-md w-full space-y-8 p-8 bg-white rounded-xl shadow-lg">
<div className="text-center">
<h2 className="text-3xl font-bold text-gray-900">إنشاء حساب</h2>
<p className="mt-2 text-gray-600">ابدأ رحلتك اليوم</p>
</div>
<form onSubmit={handleSubmit} className="space-y-6">
{error && (
<div className="bg-red-50 text-red-600 p-3 rounded-lg text-sm">
{error}
</div>
)}
<div>
<label htmlFor="name" className="block text-sm font-medium text-gray-700">
الاسم الكامل
</label>
<input
id="name"
type="text"
required
value={name}
onChange={(e) => setName(e.target.value)}
className="mt-1 block w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
placeholder="أحمد محمد"
/>
</div>
<div>
<label htmlFor="email" className="block text-sm font-medium text-gray-700">
البريد الإلكتروني
</label>
<input
id="email"
type="email"
required
value={email}
onChange={(e) => setEmail(e.target.value)}
className="mt-1 block w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
placeholder="ahmed@example.com"
/>
</div>
<div>
<label htmlFor="password" className="block text-sm font-medium text-gray-700">
كلمة المرور
</label>
<input
id="password"
type="password"
required
minLength={8}
value={password}
onChange={(e) => setPassword(e.target.value)}
className="mt-1 block w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
placeholder="8 أحرف على الأقل"
/>
</div>
<button
type="submit"
disabled={loading}
className="w-full py-3 px-4 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition disabled:opacity-50"
>
{loading ? "جاري إنشاء الحساب..." : "إنشاء حساب"}
</button>
</form>
<div className="relative">
<div className="absolute inset-0 flex items-center">
<div className="w-full border-t border-gray-300" />
</div>
<div className="relative flex justify-center text-sm">
<span className="px-2 bg-white text-gray-500">أو تابع باستخدام</span>
</div>
</div>
<div className="grid grid-cols-2 gap-3">
<button
onClick={() => signIn.social({ provider: "github" })}
className="flex items-center justify-center px-4 py-3 border border-gray-300 rounded-lg hover:bg-gray-50 transition"
>
GitHub
</button>
<button
onClick={() => signIn.social({ provider: "google" })}
className="flex items-center justify-center px-4 py-3 border border-gray-300 rounded-lg hover:bg-gray-50 transition"
>
Google
</button>
</div>
<p className="text-center text-sm text-gray-600">
لديك حساب بالفعل؟{" "}
<Link href="/auth/sign-in" className="text-blue-600 hover:underline">
تسجيل الدخول
</Link>
</p>
</div>
</div>
);
}الخطوة 8: بناء صفحة تسجيل الدخول
أنشئ نموذج تسجيل الدخول في src/app/auth/sign-in/page.tsx:
"use client";
import { useState } from "react";
import { signIn } from "@/lib/auth-client";
import { useRouter } from "next/navigation";
import Link from "next/link";
export default function SignInPage() {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [error, setError] = useState("");
const [loading, setLoading] = useState(false);
const router = useRouter();
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setError("");
setLoading(true);
const { error } = await signIn.email({
email,
password,
});
if (error) {
setError(error.message || "بيانات اعتماد غير صحيحة");
setLoading(false);
return;
}
router.push("/dashboard");
};
return (
<div className="min-h-screen flex items-center justify-center bg-gray-50">
<div className="max-w-md w-full space-y-8 p-8 bg-white rounded-xl shadow-lg">
<div className="text-center">
<h2 className="text-3xl font-bold text-gray-900">مرحباً بعودتك</h2>
<p className="mt-2 text-gray-600">سجّل الدخول إلى حسابك</p>
</div>
<form onSubmit={handleSubmit} className="space-y-6">
{error && (
<div className="bg-red-50 text-red-600 p-3 rounded-lg text-sm">
{error}
</div>
)}
<div>
<label htmlFor="email" className="block text-sm font-medium text-gray-700">
البريد الإلكتروني
</label>
<input
id="email"
type="email"
required
value={email}
onChange={(e) => setEmail(e.target.value)}
className="mt-1 block w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
/>
</div>
<div>
<label htmlFor="password" className="block text-sm font-medium text-gray-700">
كلمة المرور
</label>
<input
id="password"
type="password"
required
value={password}
onChange={(e) => setPassword(e.target.value)}
className="mt-1 block w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
/>
</div>
<div className="flex items-center justify-between">
<label className="flex items-center">
<input type="checkbox" className="rounded border-gray-300" />
<span className="ml-2 text-sm text-gray-600">تذكرني</span>
</label>
<Link href="/auth/forgot-password" className="text-sm text-blue-600 hover:underline">
نسيت كلمة المرور؟
</Link>
</div>
<button
type="submit"
disabled={loading}
className="w-full py-3 px-4 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition disabled:opacity-50"
>
{loading ? "جاري تسجيل الدخول..." : "تسجيل الدخول"}
</button>
</form>
<div className="relative">
<div className="absolute inset-0 flex items-center">
<div className="w-full border-t border-gray-300" />
</div>
<div className="relative flex justify-center text-sm">
<span className="px-2 bg-white text-gray-500">أو تابع باستخدام</span>
</div>
</div>
<div className="grid grid-cols-2 gap-3">
<button
onClick={() => signIn.social({ provider: "github" })}
className="flex items-center justify-center px-4 py-3 border border-gray-300 rounded-lg hover:bg-gray-50 transition"
>
GitHub
</button>
<button
onClick={() => signIn.social({ provider: "google" })}
className="flex items-center justify-center px-4 py-3 border border-gray-300 rounded-lg hover:bg-gray-50 transition"
>
Google
</button>
</div>
<p className="text-center text-sm text-gray-600">
ليس لديك حساب؟{" "}
<Link href="/auth/sign-up" className="text-blue-600 hover:underline">
إنشاء حساب
</Link>
</p>
</div>
</div>
);
}الخطوة 9: حماية المسارات باستخدام Middleware
أنشئ src/middleware.ts لحماية المسارات:
import { NextRequest, NextResponse } from "next/server";
import { auth } from "@/lib/auth";
import { headers } from "next/headers";
const protectedRoutes = ["/dashboard", "/settings", "/admin"];
const authRoutes = ["/auth/sign-in", "/auth/sign-up"];
export async function middleware(request: NextRequest) {
const { pathname } = request.nextUrl;
const isProtectedRoute = protectedRoutes.some((route) =>
pathname.startsWith(route)
);
const isAuthRoute = authRoutes.some((route) =>
pathname.startsWith(route)
);
const session = await auth.api.getSession({
headers: await headers(),
});
// إعادة توجيه المستخدمين غير المصادقين إلى تسجيل الدخول
if (isProtectedRoute && !session) {
const signInUrl = new URL("/auth/sign-in", request.url);
signInUrl.searchParams.set("callbackUrl", pathname);
return NextResponse.redirect(signInUrl);
}
// إعادة توجيه المستخدمين المصادقين بعيداً عن صفحات المصادقة
if (isAuthRoute && session) {
return NextResponse.redirect(new URL("/dashboard", request.url));
}
return NextResponse.next();
}
export const config = {
matcher: ["/dashboard/:path*", "/settings/:path*", "/admin/:path*", "/auth/:path*"],
};الخطوة 10: بناء لوحة التحكم مع بيانات الجلسة
أنشئ لوحة تحكم محمية في src/app/dashboard/page.tsx:
import { auth } from "@/lib/auth";
import { headers } from "next/headers";
import { redirect } from "next/navigation";
import { SignOutButton } from "@/components/sign-out-button";
export default async function DashboardPage() {
const session = await auth.api.getSession({
headers: await headers(),
});
if (!session) {
redirect("/auth/sign-in");
}
const { user } = session;
return (
<div className="min-h-screen bg-gray-50">
<nav className="bg-white shadow-sm border-b">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex justify-between h-16 items-center">
<h1 className="text-xl font-semibold">لوحة التحكم</h1>
<div className="flex items-center gap-4">
<span className="text-sm text-gray-600">{user.email}</span>
<SignOutButton />
</div>
</div>
</div>
</nav>
<main className="max-w-7xl mx-auto py-12 px-4 sm:px-6 lg:px-8">
<div className="bg-white rounded-xl shadow-sm p-8">
<h2 className="text-2xl font-bold mb-6">
مرحباً، {user.name}!
</h2>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
<div className="p-6 bg-blue-50 rounded-lg">
<h3 className="font-semibold text-blue-900">الملف الشخصي</h3>
<p className="text-sm text-blue-700 mt-1">{user.email}</p>
<p className="text-xs text-blue-600 mt-2">
مُوثّق: {user.emailVerified ? "نعم" : "لا"}
</p>
</div>
<div className="p-6 bg-green-50 rounded-lg">
<h3 className="font-semibold text-green-900">الجلسة</h3>
<p className="text-sm text-green-700 mt-1">نشطة</p>
<p className="text-xs text-green-600 mt-2">
تنتهي: {new Date(session.session.expiresAt).toLocaleDateString("ar")}
</p>
</div>
<div className="p-6 bg-purple-50 rounded-lg">
<h3 className="font-semibold text-purple-900">الحساب</h3>
<p className="text-sm text-purple-700 mt-1">
المعرف: {user.id.slice(0, 8)}...
</p>
<p className="text-xs text-purple-600 mt-2">
انضم: {new Date(user.createdAt).toLocaleDateString("ar")}
</p>
</div>
</div>
</div>
</main>
</div>
);
}أنشئ مكون زر تسجيل الخروج في src/components/sign-out-button.tsx:
"use client";
import { signOut } from "@/lib/auth-client";
import { useRouter } from "next/navigation";
export function SignOutButton() {
const router = useRouter();
return (
<button
onClick={async () => {
await signOut();
router.push("/auth/sign-in");
}}
className="px-4 py-2 text-sm bg-red-50 text-red-600 rounded-lg hover:bg-red-100 transition"
>
تسجيل الخروج
</button>
);
}الخطوة 11: إضافة التحكم بالوصول المبني على الأدوار
يحتوي Better Auth على إضافة RBAC مدمجة. حدّث src/lib/auth.ts:
import { betterAuth } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { admin } from "better-auth/plugins";
import { db } from "@/db";
export const auth = betterAuth({
database: drizzleAdapter(db, {
provider: "pg",
}),
emailAndPassword: {
enabled: true,
},
socialProviders: {
github: {
clientId: process.env.GITHUB_CLIENT_ID!,
clientSecret: process.env.GITHUB_CLIENT_SECRET!,
},
google: {
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
},
},
plugins: [
admin(), // يضيف حقلي role وbanned إلى المستخدم
],
session: {
expiresIn: 60 * 60 * 24 * 7,
updateAge: 60 * 60 * 24,
cookieCache: {
enabled: true,
maxAge: 5 * 60,
},
},
});حدّث عميل المصادقة ليشمل إضافة admin في src/lib/auth-client.ts:
import { createAuthClient } from "better-auth/react";
import { adminClient } from "better-auth/client/plugins";
export const authClient = createAuthClient({
baseURL: process.env.NEXT_PUBLIC_APP_URL || "http://localhost:3000",
plugins: [adminClient()],
});
export const { signIn, signUp, signOut, useSession } = authClient;الآن يمكنك التحقق من الأدوار في مكوناتك:
// Server Component
const session = await auth.api.getSession({
headers: await headers(),
});
if (session?.user.role !== "admin") {
redirect("/dashboard");
}
// Client Component
const { data: session } = useSession();
if (session?.user.role === "admin") {
// إظهار أدوات المسؤول
}أنشئ صفحة المسؤول في src/app/admin/page.tsx:
import { auth } from "@/lib/auth";
import { headers } from "next/headers";
import { redirect } from "next/navigation";
export default async function AdminPage() {
const session = await auth.api.getSession({
headers: await headers(),
});
if (!session || session.user.role !== "admin") {
redirect("/dashboard");
}
const users = await auth.api.listUsers({
headers: await headers(),
});
return (
<div className="min-h-screen bg-gray-50 p-8">
<h1 className="text-3xl font-bold mb-8">لوحة المسؤول</h1>
<div className="bg-white rounded-xl shadow-sm overflow-hidden">
<table className="w-full">
<thead className="bg-gray-50">
<tr>
<th className="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase">
الاسم
</th>
<th className="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase">
البريد الإلكتروني
</th>
<th className="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase">
الدور
</th>
<th className="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase">
تاريخ الانضمام
</th>
</tr>
</thead>
<tbody className="divide-y divide-gray-200">
{users?.users?.map((user) => (
<tr key={user.id}>
<td className="px-6 py-4 text-sm">{user.name}</td>
<td className="px-6 py-4 text-sm text-gray-600">{user.email}</td>
<td className="px-6 py-4">
<span className={`text-xs px-2 py-1 rounded-full ${
user.role === "admin"
? "bg-purple-100 text-purple-800"
: "bg-gray-100 text-gray-800"
}`}>
{user.role || "مستخدم"}
</span>
</td>
<td className="px-6 py-4 text-sm text-gray-600">
{new Date(user.createdAt).toLocaleDateString("ar")}
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
);
}الخطوة 12: إضافة التحقق من البريد الإلكتروني
حدّث إعداد المصادقة لتفعيل التحقق من البريد الإلكتروني:
import { betterAuth } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { admin } from "better-auth/plugins";
import { db } from "@/db";
export const auth = betterAuth({
database: drizzleAdapter(db, {
provider: "pg",
}),
emailAndPassword: {
enabled: true,
requireEmailVerification: true,
sendVerificationEmail: async ({ user, url }) => {
// في الإنتاج، استخدم خدمة مثل Resend أو SendGrid أو AWS SES
console.log(`رابط التحقق لـ ${user.email}: ${url}`);
// مثال مع Resend:
// await resend.emails.send({
// from: "auth@yourdomain.com",
// to: user.email,
// subject: "تحقق من بريدك الإلكتروني",
// html: `<a href="${url}">تحقق من البريد</a>`,
// });
},
sendResetPassword: async ({ user, url }) => {
console.log(`إعادة تعيين كلمة المرور لـ ${user.email}: ${url}`);
},
},
socialProviders: {
github: {
clientId: process.env.GITHUB_CLIENT_ID!,
clientSecret: process.env.GITHUB_CLIENT_SECRET!,
},
google: {
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
},
},
plugins: [admin()],
session: {
expiresIn: 60 * 60 * 24 * 7,
updateAge: 60 * 60 * 24,
cookieCache: {
enabled: true,
maxAge: 5 * 60,
},
},
});الخطوة 13: بناء تدفق نسيان كلمة المرور
أنشئ src/app/auth/forgot-password/page.tsx:
"use client";
import { useState } from "react";
import { authClient } from "@/lib/auth-client";
import Link from "next/link";
export default function ForgotPasswordPage() {
const [email, setEmail] = useState("");
const [sent, setSent] = useState(false);
const [loading, setLoading] = useState(false);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setLoading(true);
await authClient.forgetPassword({
email,
redirectTo: "/auth/reset-password",
});
setSent(true);
setLoading(false);
};
if (sent) {
return (
<div className="min-h-screen flex items-center justify-center bg-gray-50">
<div className="max-w-md w-full p-8 bg-white rounded-xl shadow-lg text-center">
<h2 className="text-2xl font-bold text-gray-900 mb-4">تحقق من بريدك</h2>
<p className="text-gray-600 mb-6">
إذا كان هناك حساب مرتبط بـ {email}، ستتلقى رابط إعادة تعيين كلمة المرور.
</p>
<Link href="/auth/sign-in" className="text-blue-600 hover:underline">
العودة لتسجيل الدخول
</Link>
</div>
</div>
);
}
return (
<div className="min-h-screen flex items-center justify-center bg-gray-50">
<div className="max-w-md w-full space-y-8 p-8 bg-white rounded-xl shadow-lg">
<div className="text-center">
<h2 className="text-3xl font-bold text-gray-900">نسيت كلمة المرور</h2>
<p className="mt-2 text-gray-600">
أدخل بريدك الإلكتروني وسنرسل لك رابط إعادة التعيين
</p>
</div>
<form onSubmit={handleSubmit} className="space-y-6">
<div>
<label htmlFor="email" className="block text-sm font-medium text-gray-700">
البريد الإلكتروني
</label>
<input
id="email"
type="email"
required
value={email}
onChange={(e) => setEmail(e.target.value)}
className="mt-1 block w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
/>
</div>
<button
type="submit"
disabled={loading}
className="w-full py-3 px-4 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition disabled:opacity-50"
>
{loading ? "جاري الإرسال..." : "إرسال رابط إعادة التعيين"}
</button>
</form>
<p className="text-center text-sm text-gray-600">
<Link href="/auth/sign-in" className="text-blue-600 hover:underline">
العودة لتسجيل الدخول
</Link>
</p>
</div>
</div>
);
}الخطوة 14: إضافة hook لجلسة المستخدم في مكونات العميل
يوفر Better Auth hook جاهزاً لـ React. إليك كيفية استخدامه في أي مكون عميل:
"use client";
import { useSession } from "@/lib/auth-client";
export function UserAvatar() {
const { data: session, isPending } = useSession();
if (isPending) {
return <div className="w-8 h-8 rounded-full bg-gray-200 animate-pulse" />;
}
if (!session) {
return null;
}
return (
<div className="flex items-center gap-2">
{session.user.image ? (
<img
src={session.user.image}
alt={session.user.name}
className="w-8 h-8 rounded-full"
/>
) : (
<div className="w-8 h-8 rounded-full bg-blue-500 flex items-center justify-center text-white text-sm font-bold">
{session.user.name.charAt(0).toUpperCase()}
</div>
)}
<span className="text-sm font-medium">{session.user.name}</span>
</div>
);
}اختبار التطبيق
شغّل خادم التطوير:
npm run devاختبر التدفقات التالية:
- التسجيل: انتقل إلى
/auth/sign-up، أنشئ حساباً بالبريد الإلكتروني وكلمة المرور - تسجيل الدخول: انتقل إلى
/auth/sign-in، سجّل الدخول ببياناتك - لوحة التحكم: تحقق من ظهور بيانات المستخدم في
/dashboard - تسجيل الخروج: انقر على زر تسجيل الخروج، تحقق من إعادة التوجيه
- OAuth: اختبر أزرار تسجيل الدخول بـ GitHub وGoogle
- المسارات المحمية: حاول الوصول إلى
/dashboardبدون تسجيل دخول — يجب إعادة التوجيه - مسارات المصادقة: حاول الوصول إلى
/auth/sign-inأثناء تسجيل الدخول — يجب إعادة التوجيه للوحة التحكم
حل المشكلات الشائعة
أخطاء "جلسة غير صالحة"
تأكد من تعيين BETTER_AUTH_SECRET وثباته عبر عمليات إعادة التشغيل. إذا غيّرته، تصبح جميع الجلسات الموجودة غير صالحة.
أخطاء استدعاء OAuth
تحقق من تطابق عناوين URL في إعدادات OAuth مع:
- GitHub:
http://localhost:3000/api/auth/callback/github - Google:
http://localhost:3000/api/auth/callback/google
مشاكل الاتصال بقاعدة البيانات
تأكد من صحة DATABASE_URL وإمكانية الوصول إلى قاعدة البيانات. نفّذ npx drizzle-kit push للتأكد من وجود الجداول.
عدم استمرار الجلسة
تحقق من تعيين ملفات تعريف الارتباط بشكل صحيح. يستخدم Better Auth بادئة __Secure- في الإنتاج — تأكد من دعم نطاقك لـ HTTPS.
قائمة التحقق للإنتاج
قبل النشر في الإنتاج:
- تعيين
BETTER_AUTH_SECRETقوي (32 حرفاً على الأقل) - تفعيل
requireEmailVerification - إعداد مزود بريد حقيقي (Resend أو SendGrid)
- تعيين
BETTER_AUTH_URLلنطاق الإنتاج - تفعيل HTTPS (مطلوب لملفات تعريف الارتباط الآمنة)
- إعداد تحديد معدل الطلبات على نقاط نهاية المصادقة
- إعداد رؤوس CORS المناسبة إذا كنت تستخدم واجهة أمامية منفصلة
- اختبار جميع عناوين OAuth مع نطاقات الإنتاج
- تفعيل التخزين المؤقت لملفات تعريف الارتباط للأداء
الخطوات التالية
الآن بعد أن لديك نظام مصادقة متين، فكّر في:
- المصادقة الثنائية: أضف إضافة
twoFactorللمصادقة بـ TOTP - الروابط السحرية: فعّل المصادقة بدون كلمة مرور عبر البريد
- دعم المنظمات: استخدم إضافة
organizationللتطبيقات متعددة المستأجرين - تحديد المعدل: أضف إضافة
rateLimitلمنع هجمات القوة الغاشمة - مفاتيح المرور: فعّل مصادقة WebAuthn للدخول بدون كلمة مرور
الخلاصة
يوفر Better Auth حلاً قوياً وآمناً ومرناً للمصادقة في تطبيقات Next.js. على عكس الحلول المقيّدة، يمنحك تحكماً كاملاً في مخطط قاعدة البيانات وإدارة الجلسات وتجربة المستخدم مع التعامل مع تفاصيل الأمان المعقدة نيابةً عنك.
في هذا الدليل، بنيت نظام مصادقة متكاملاً مع البريد الإلكتروني وكلمة المرور وOAuth وحماية المسارات والتحكم بالأدوار والتحقق من البريد. بنية الإضافات تعني أنك تستطيع إضافة ميزات تدريجياً مثل المصادقة الثنائية والروابط السحرية والمنظمات مع نمو تطبيقك.
أصبح Better Auth الخيار الأول في 2026 للمطورين الذين يريدون مصادقة سهلة الإعداد وقوية بما يكفي للإنتاج — بدون التبعية لأي مزود خدمة.
ناقش مشروعك معنا
نحن هنا للمساعدة في احتياجات تطوير الويب الخاصة بك. حدد موعدًا لمناقشة مشروعك وكيف يمكننا مساعدتك.
دعنا نجد أفضل الحلول لاحتياجاتك.
مقالات ذات صلة

إضافة المصادقة لتطبيق Next.js 15 باستخدام Auth.js v5: البريد الإلكتروني وOAuth والتحكم بالأدوار
تعلم كيفية إضافة نظام مصادقة جاهز للإنتاج لتطبيق Next.js 15 باستخدام Auth.js v5. يغطي هذا الدليل الشامل تسجيل الدخول عبر Google OAuth وبيانات الاعتماد بالبريد الإلكتروني وكلمة المرور والمسارات المحمية والتحكم بالوصول حسب الأدوار.

بناء مشروع SaaS متكامل باستخدام Next.js 15 و Stripe و Auth.js v5
تعلم كيفية بناء تطبيق SaaS جاهز للإنتاج باستخدام Next.js 15 مع نظام اشتراكات Stripe ومصادقة Auth.js v5. يغطي هذا الدليل التفصيلي إعداد المشروع وتسجيل الدخول عبر OAuth وخطط الأسعار ومعالجة Webhooks والمسارات المحمية.

بناء وكيل ذكاء اصطناعي مستقل باستخدام Agentic RAG و Next.js
تعلم كيف تبني وكيل ذكاء اصطناعي يقرر بشكل مستقل متى وكيف يسترجع المعلومات من قواعد البيانات المتجهية. دليل عملي شامل باستخدام Vercel AI SDK و Next.js مع أمثلة قابلة للتنفيذ.