Next.js

المصادقة في Next.js — دليل شامل 2026

📅 2026-11-18⏱ 9 دقائق قراءة
في المقال السابق، تعلمت Styling. الآن سنتعلم **المصادقة (Authentication)** — وهي **الميزة الأكثر تعقيداً** في أي تطبيق حقيقي. في هذا الدليل العملي، سنأخذك خطوة بخطوة لفهم المصادقة في Next.js، مع تمارين وحلول. ## ما هي المصادقة؟ **المصادقة (Authentication)** = التحقق من **هوية المستخدم**. **التفويض (Authorization)** = التحقق من **صلاحياته**. **مثال:** - **Authentication:** تسجيل الدخول باسم المستخدم وكلمة المرور. - **Authorization:** هل هذا المستخدم مدير؟ ## أنواع المصادقة <table> <thead> <tr> <th>النوع</th> <th>الوصف</th> <th>الاستخدام</th> </tr> </thead> <tbody> <tr> <td><strong>Sessions</strong></td> <td>جلسة على السيرفر</td> <td>تطبيقات تقليدية</td> </tr> <tr> <td><strong>JWT</strong></td> <td>توكن مشفر</td> <td>APIs و SPAs</td> </tr> <tr> <td><strong>OAuth</strong></td> <td>مزود خارجي</td> <td>Google, GitHub</td> </tr> <tr> <td><strong>Magic Links</strong></td> <td>رابط بالبريد</td> <td>تطبيقات حديثة</td> </tr> <tr> <td><strong>2FA</strong></td> <td>عاملان</td> <td>أمان عالٍ</td> </tr> </tbody> </table> ## NextAuth.js (Auth.js) **NextAuth.js** (الآن Auth.js) هو الحل **الرسمي** للمصادقة في Next.js. ### المميزات: - ✅ يدعم **عدة مزودين** (Google, GitHub, Credentials). - ✅ **Sessions** و **JWT**. - ✅ **Middleware** مدمج. - ✅ **Hooks** جاهزة. - ✅ **TypeScript**. - ✅ **قواعد بيانات** متعددة. ## تثبيت NextAuth ```bash npm install next-auth@beta ``` **⚠️ ملاحظة:** `@beta` لأننا نستخدم Next.js 15 (App Router). ## الإعداد الأساسي ### 1. `.env.local` ```bash NEXTAUTH_SECRET=your-super-secret-key-here NEXTAUTH_URL=http://localhost:3000 # لمزود Google GOOGLE_CLIENT_ID=your-google-client-id GOOGLE_CLIENT_SECRET=your-google-client-secret # لمزود GitHub GITHUB_ID=your-github-id GITHUB_SECRET=your-github-secret ``` **لإنشاء `NEXTAUTH_SECRET`:** ```bash openssl rand -base64 32 ``` ### 2. `app/api/auth/[...nextauth]/route.ts` ```tsx import NextAuth from "next-auth"; import GoogleProvider from "next-auth/providers/google"; import GitHubProvider from "next-auth/providers/github"; import CredentialsProvider from "next-auth/providers/credentials"; const handler = NextAuth({ providers: [ GoogleProvider({ clientId: process.env.GOOGLE_CLIENT_ID!, clientSecret: process.env.GOOGLE_CLIENT_SECRET!, }), GitHubProvider({ clientId: process.env.GITHUB_ID!, clientSecret: process.env.GITHUB_SECRET!, }), CredentialsProvider({ name: "Credentials", credentials: { email: { label: "Email", type: "email" }, password: { label: "Password", type: "password" }, }, async authorize(credentials) { // هنا نتحقق من البيانات if (credentials?.email === "[email protected]" && credentials?.password === "password") { return { id: "1", name: "أحمد", email: "[email protected]" }; } return null; }, }), ], pages: { signIn: "/login", error: "/login", }, session: { strategy: "jwt", }, callbacks: { async jwt({ token, user }) { if (user) { token.id = user.id; } return token; }, async session({ session, token }) { if (session.user) { (session.user as any).id = token.id; } return session; }, }, }); export { handler as GET, handler as POST }; ``` ### 3. SessionProvider **`app/providers.tsx`:** ```tsx "use client"; import { SessionProvider } from "next-auth/react"; export default function Providers({ children }: { children: React.ReactNode }) { return <SessionProvider>{children}</SessionProvider>; } ``` ### 4. `app/layout.tsx` ```tsx import Providers from "./providers"; export default function RootLayout({ children }: { children: React.ReactNode }) { return ( <html lang="ar" dir="rtl"> <body> <Providers>{children}</Providers> </body> </html> ); } ``` ## استخدام المصادقة ### 1. في Server Component ```tsx import { getServerSession } from "next-auth"; import { authOptions } from "@/app/api/auth/[...nextauth]/route"; export default async function ProfilePage() { const session = await getServerSession(authOptions); if (!session) { return <p>يجب تسجيل الدخول</p>; } return ( <div> <h1>مرحباً {session.user?.name}</h1> <p>{session.user?.email}</p> </div> ); } ``` ### 2. في Client Component ```tsx "use client"; import { useSession, signIn, signOut } from "next-auth/react"; export default function AuthButton() { const { data: session, status } = useSession(); if (status === "loading") { return <p>جاري التحميل...</p>; } if (session) { return ( <div className="flex items-center gap-4"> <span>مرحباً {session.user?.name}</span> <button onClick={() => signOut()} className="bg-red-600 text-white px-4 py-2 rounded-lg" > خروج </button> </div> ); } return ( <button onClick={() => signIn()} className="bg-blue-600 text-white px-4 py-2 rounded-lg" > تسجيل الدخول </button> ); } ``` ## صفحات تسجيل الدخول ### `app/login/page.tsx` ```tsx "use client"; import { signIn } from "next-auth/react"; import { useState } from "react"; export default function LoginPage() { const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const [error, setError] = useState(""); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); setError(""); const res = await signIn("credentials", { email, password, redirect: false, }); if (res?.error) { setError("بيانات الدخول غير صحيحة"); } else { window.location.href = "/dashboard"; } }; return ( <div className="min-h-screen flex items-center justify-center bg-gray-50"> <form onSubmit={handleSubmit} className="bg-white p-8 rounded-2xl shadow-lg w-full max-w-md" > <h1 className="text-2xl font-bold text-center mb-6">تسجيل الدخول</h1> {error && ( <div className="bg-red-100 text-red-700 p-3 rounded-lg mb-4"> {error} </div> )} <input type="email" placeholder="البريد" value={email} onChange={(e) => setEmail(e.target.value)} required className="w-full px-4 py-3 border rounded-lg mb-4" /> <input type="password" placeholder="كلمة المرور" value={password} onChange={(e) => setPassword(e.target.value)} required className="w-full px-4 py-3 border rounded-lg mb-4" /> <button type="submit" className="w-full bg-blue-600 text-white py-3 rounded-lg font-bold hover:bg-blue-700 transition mb-4" > دخول </button> <div className="border-t pt-4"> <p className="text-center text-sm text-gray-500 mb-4"> أو سجل باستخدام </p> <div className="flex gap-2"> <button type="button" onClick={() => signIn("google", { callbackUrl: "/dashboard" })} className="flex-1 bg-white border-2 border-gray-200 py-3 rounded-lg hover:border-gray-300 transition" > 🔵 Google </button> <button type="button" onClick={() => signIn("github", { callbackUrl: "/dashboard" })} className="flex-1 bg-gray-900 text-white py-3 rounded-lg hover:bg-gray-800 transition" > ⚫ GitHub </button> </div> </div> </form> </div> ); } ``` ## حماية المسارات (Middleware) **`middleware.ts`** (في جذر المشروع): ```tsx import { withAuth } from "next-auth/middleware"; export default withAuth({ pages: { signIn: "/login", }, }); export const config = { matcher: ["/dashboard/:path*", "/profile/:path*", "/admin/:path*"], }; ``` **النتيجة:** أي وصول لـ `/dashboard/*` يتطلب تسجيل دخول. ## حماية المسارات (بدون Middleware) **`app/dashboard/page.tsx`:** ```tsx import { getServerSession } from "next-auth"; import { redirect } from "next/navigation"; import { authOptions } from "@/app/api/auth/[...nextauth]/route"; export default async function DashboardPage() { const session = await getServerSession(authOptions); if (!session) { redirect("/login"); } return ( <div> <h1>لوحة التحكم</h1> <p>مرحباً {session.user?.name}</p> </div> ); } ``` ## Types (TypeScript) **`types/next-auth.d.ts`:** ```typescript import NextAuth from "next-auth"; declare module "next-auth" { interface Session { user: { id: string; name: string; email: string; role?: string; }; } interface User { id: string; role?: string; } } declare module "next-auth/jwt" { interface JWT { id: string; role?: string; } } ``` ## نموذج كامل مع قاعدة بيانات ### 1. `lib/auth.ts` ```typescript import CredentialsProvider from "next-auth/providers/credentials"; import bcrypt from "bcryptjs"; import { prisma } from "./prisma"; export const authOptions = { providers: [ CredentialsProvider({ name: "Credentials", credentials: { email: { label: "Email", type: "email" }, password: { label: "Password", type: "password" }, }, async authorize(credentials) { if (!credentials?.email || !credentials?.password) { return null; } const user = await prisma.user.findUnique({ where: { email: credentials.email }, }); if (!user) return null; const isValid = await bcrypt.compare( credentials.password, user.password ); if (!isValid) return null; return { id: user.id, name: user.name, email: user.email, role: user.role, }; }, }), ], session: { strategy: "jwt" as const }, callbacks: { async jwt({ token, user }: any) { if (user) { token.id = user.id; token.role = user.role; } return token; }, async session({ session, token }: any) { if (session.user) { session.user.id = token.id; session.user.role = token.role; } return session; }, }, pages: { signIn: "/login", }, }; ``` ## استخدام الدوال المساعدة **`lib/auth-helpers.ts`:** ```typescript import { getServerSession } from "next-auth"; import { redirect } from "next/navigation"; import { authOptions } from "./auth"; export async function requireAuth() { const session = await getServerSession(authOptions); if (!session) redirect("/login"); return session; } export async function requireAdmin() { const session = await getServerSession(authOptions); if (!session) redirect("/login"); if (session.user.role !== "admin") redirect("/"); return session; } ``` **الاستخدام:** ```tsx export default async function DashboardPage() { const session = await requireAuth(); return <h1>مرحباً {session.user.name}</h1>; } ``` ## تمارين عملية ### تمرين 1: إعداد NextAuth أعدّ NextAuth مع Google. **الحل:** ```tsx const handler = NextAuth({ providers: [ GoogleProvider({ clientId: process.env.GOOGLE_CLIENT_ID!, clientSecret: process.env.GOOGLE_CLIENT_SECRET!, }), ], }); ``` ### تمرين 2: زر تسجيل الدخول أنشئ زر تسجيل دخول. **الحل:** ```tsx "use client"; import { signIn } from "next-auth/react"; export default function LoginButton() { return <button onClick={() => signIn()}>تسجيل الدخول</button>; } ``` ### تمرين 3: عرض Session اعرض بيانات المستخدم. **الحل:** ```tsx "use client"; import { useSession } from "next-auth/react"; export default function Profile() { const { data: session } = useSession(); if (!session) return <p>غير مسجل</p>; return <p>مرحباً {session.user?.name}</p>; } ``` ### تمرين 4: حماية صفحة احمِ `/dashboard`. **الحل:** ```tsx import { getServerSession } from "next-auth"; import { redirect } from "next/navigation"; import { authOptions } from "@/lib/auth"; export default async function Page() { const session = await getServerSession(authOptions); if (!session) redirect("/login"); return <h1>محمي</h1>; } ``` ### تمرين 5: Middleware احمِ `/admin/*`. **الحل:** ```tsx import { withAuth } from "next-auth/middleware"; export default withAuth(); export const config = { matcher: ["/admin/:path*"] }; ``` ### تمرين 6: تسجيل الخروج أضف زر خروج. **الحل:** ```tsx "use client"; import { signOut } from "next-auth/react"; export default function LogoutButton() { return <button onClick={() => signOut()}>خروج</button>; } ``` ### تمرين 7: صفحة تسجيل دخول مخصصة أنشئ `/login`. **الحل:** (راجع المثال أعلاه) ### تمرين 8: تطبيق كامل ابنِ تطبيق مع مصادقة كاملة. **الحل:** (راجع الأمثلة أعلاه) ## حل المشاكل الشائعة ### 🔴 المشكلة 1: `NEXTAUTH_SECRET is missing` **الحل:** أضف `NEXTAUTH_SECRET` في `.env.local`. ### 🔴 المشكلة 2: `signIn` لا يعمل **السبب:** لم تُضف `SessionProvider`. **الحل:** أضفها في `layout.tsx`. ### 🔴 المشكلة 3: Session فارغ **الحل:** تأكد من `getServerSession(authOptions)`. ### 🔴 المشكلة 4: Middleware لا يعمل **السبب:** `matcher` ناقص. **الحل:** ```tsx export const config = { matcher: ["/dashboard/:path*"], }; ``` ### 🔴 المشكلة 5: `useSession` لا يعمل **السبب:** لم تستخدم `SessionProvider`. **الحل:** أضفه في `providers.tsx`. ## جدول دوال NextAuth <table> <thead> <tr> <th>الدالة</th> <th>الوظيفة</th> </tr> </thead> <tbody> <tr> <td><code>signIn()</code></td> <td>تسجيل الدخول</td> </tr> <tr> <td><code>signOut()</code></td> <td>تسجيل الخروج</td> </tr> <tr> <td><code>useSession()</code></td> <td>الحصول على Session</td> </tr> <tr> <td><code>getServerSession()</code></td> <td>Session على السيرفر</td> </tr> <tr> <td><code>withAuth()</code></td> <td>Middleware</td> </tr> </tbody> </table> ## قائمة تحقق نهائية <table> <thead> <tr> <th>المهمة</th> <th>الحالة</th> </tr> </thead> <tbody> <tr> <td>تثبيت NextAuth</td> <td>⬜</td> </tr> <tr> <td>إعداد المزودين</td> <td>⬜</td> </tr> <tr> <td>صفحة تسجيل دخول</td> <td>⬜</td> </tr> <tr> <td>حماية المسارات</td> <td>⬜</td> </tr> <tr> <td>Middleware</td> <td>⬜</td> </tr> <tr> <td>TypeScript Types</td> <td>⬜</td> </tr> <tr> <td>حل التمارين الثمانية</td> <td>⬜</td> </tr> </tbody> </table> ## ماذا بعد هذا المقال؟ الآن بعد أن أتقنت المصادقة، أنت جاهز للمقال التالي: 1. **Deployment** — النشر. 2. **مشروع كامل** — مدونة متكاملة. ## الخلاصة في هذا المقال، تعلمت: - ✅ ما هي المصادقة. - ✅ NextAuth.js. - ✅ Credentials و OAuth. - ✅ Sessions و JWT. - ✅ حماية المسارات. - ✅ Middleware. - ✅ TypeScript Types. - ✅ حل 8 تمارين عملية. **تذكر:** المصادقة هي **أعقد ميزة** — لكن NextAuth يجعلها سهلة.