Next.js: المصادقة والتفويض

آخر تحديث: 2026-08-26

إضافة المصادقة إلى تطبيق full-stack يشبه تركيب نظام تحكم في الوصول في مبنى إداري — تحتاج إلى تحكم دقيق في من يمكنه الدخول والطوابق التي يمكنه الوصول إليها.

1. ما ستتعلمه



2. قصة حقيقية لمطور Full-Stack

(1) نقطة الألم: لم ندرك أننا نفتقر إلى المصادقة حتى ما قبل الإطلاق مباشرة

تعمل Alice في شركة SaaS ناشئة مكونة من 15 شخصًا. طورت "TaskFlow"، منصة تعاون جماعي كاملة الميزات، باستخدام Next.js 16. لكن أثناء تدقيق أمني قبل الإطلاق، أشار Bob، القائد التقني، إلى:

"صفحة /dashboard الخاصة بك متاحة لأي شخص؛ نقطة نهاية API تفتقر إلى مصادقة token؛ بيانات المستخدم مكشوفة بنص صريح."

يسرد تقرير التدقيق المشكلات التالية:

المشكلة النطاق مستوى الخطر
لا توجد صفحة تسجيل دخول جميع المسارات 🔴 مرتفع
مسار API: لا توجد مصادقة /api/projects/* 🔴 مرتفع
لا يوجد تمييز بين الأدوار يمكن لجميع المستخدمين رؤية لوحة admin 🟡 متوسط
session لا تنتهي أبدًا تسجيل دخول واحد ويبقى مسجلًا للأبد 🔴 مرتفع

(2) حل Auth.js + Clerk

نفذ تدفق المصادقة القياسي باستخدام Auth.js v5، مع Clerk كبديل بدون تكوين.

TS
// app/api/auth/[...nextauth]/route.ts
import NextAuth from 'next-auth'
import GitHub from 'next-auth/providers/github'
import Google from 'next-auth/providers/google'
import Credentials from 'next-auth/providers/credentials'

export const { handlers, signIn, signOut, auth } = NextAuth({
  providers: [
    GitHub,
    Google,
    Credentials({
      credentials: { email: {}, password: {} },
      async authorize(credentials) {
        const user = { id: '1', name: 'Alice', email: 'alice@taskflow.io', role: 'admin' }
        return credentials.email === 'alice@taskflow.io' && credentials.password === 'pass123' ? user : null
      }
    })
  ],
  callbacks: { session: ({ session, token }) => ({ ...session, user: { ...session.user, role: token.role } }) }
})

(3) النتائج

البعد قبل التنفيذ بعد التنفيذ
الوصول إلى /dashboard بدون تسجيل دخول متاح إعادة توجيه إلى صفحة تسجيل الدخول
أمان نقطة نهاية API لا يوجد تحقق تحقق getToken() من JWT
التحكم في الأدوار لا يوجد ثلاثة مستويات: admin، editor، viewer
انتهاء صلاحية session دائمة تنتهي تلقائيًا بعد 30 يومًا
وقت الدمج Auth.js: ساعتين / Clerk: 30 دقيقة


3. دمج مزودي Auth.js v5 (NextAuth)

(1) هندسة المزودين

تتيح لك طبقة تجريد Provider في Auth.js v5 التكامل مع مصادر مصادقة متعددة عبر واجهة موحدة:

100%
graph TB
    A[طلب المصادقة] --> B{NextAuth Route Handler}
    B --> C[Credentials<br/>بريد إلكتروني + كلمة مرور]
    B --> D[OAuth<br/>GitHub / Google]
    B --> E[مزود آخر<br/>Auth0 / Azure AD]
    C --> F[استدعاء JWT<br/>token + session]
    D --> F
    E --> F
    F --> G[إعادة Session إلى العميل]
    G --> H[تحقق Middleware]
    H --> I[صفحة محمية]
    H --> J[صفحة عامة]

    style B fill:#cce5ff
    style F fill:#d4edda
نوع المزود تعقيد التنفيذ تجربة المستخدم السيناريوهات المناسبة
Credentials متوسط (يتطلب صفحة تسجيل دخول مخصصة) نموذج قياسي نظام حسابات خاص
GitHub OAuth منخفض تسجيل دخول بنقرة واحدة أدوات المطورين
Google OAuth منخفض تسجيل دخول بنقرة واحدة للمستخدمين العامين
OIDC / SAML مرتفع دخول موحد مؤسسي الشبكات الداخلية للمؤسسات

(2) التثبيت والتهيئة

BASH
npm install next-auth@beta
npx auth secret    # إنشاء AUTH_SECRET
TS
// auth.ts — إعداد المصادقة المركزي
import NextAuth from 'next-auth'
import GitHub from 'next-auth/providers/github'
import Google from 'next-auth/providers/google'

export const { handlers, signIn, signOut, auth } = NextAuth({
  providers: [
    GitHub({ clientId: process.env.GITHUB_ID!, clientSecret: process.env.GITHUB_SECRET! }),
    Google({ clientId: process.env.GOOGLE_ID!, clientSecret: process.env.GOOGLE_SECRET! })
  ]
})

▶ مثال: التكوين الكامل لمزود Credentials

المخرجات:

TEXT 📖 للعرض فقط
تم تنفيذ كود TypeScript بنجاح.
TS
// auth.ts — مزج Credentials + OAuth
import NextAuth from 'next-auth'
import Credentials from 'next-auth/providers/credentials'
import GitHub from 'next-auth/providers/github'

export const { handlers, signIn, signOut, auth } = NextAuth({
  providers: [
    Credentials({
      name: 'credentials',
      credentials: {
        email: { label: 'البريد الإلكتروني', type: 'email' },
        password: { label: 'كلمة المرور', type: 'password' }
      },
      async authorize(credentials) {
        const { email, password } = credentials as { email: string; password: string }
        // في المشروع الفعلي: استعلام قاعدة البيانات
        const user = { id: '1', name: 'Alice', email, role: 'admin' }
        if (email === 'admin@taskflow.io' && password === 'admin123') return user
        return null
      }
    }),
    GitHub
  ],
  callbacks: {
    async jwt({ token, user }) {
      if (user) token.role = (user as any).role
      return token
    },
    async session({ session, token }) {
      session.user.role = token.role as string
      return session
    }
  }
})

المخرجات:

TEXT 📖 للعرض فقط
تم تحميل تكوين TypeScript بنجاح.
💡 نصيحة: إذا أعادت دالة authorize القيمة null، فهذا يعني فشل تسجيل الدخول، وسيعيد Auth.js تلقائيًا استجابة 401.



4. إدارة Session: JWT مقابل Database

(1) مقارنة بين الاستراتيجيتين

يدعم Auth.js v5 استراتيجيتين لتخزين session:

البعد JWT (الافتراضي) Database
موقع التخزين JWT مشفرة في cookie جدول Session في قاعدة البيانات
عبء الاستعلام صفر (بدون استعلامات DB) استعلام DB واحد لكل طلب
الإلغاء الفوري يعتمد على وقت انتهاء صلاحية JWT يمكن الإلغاء فورًا
قابلية التوسع لا حاجة لقاعدة بيانات يتطلب ORM مثل Prisma
النطاق المناسب تطبيقات صغيرة ومتوسطة تطبيقات مؤسسية كبيرة
100%
graph LR
    subgraph نموذج JWT
        A1[تسجيل الدخول] --> B1[إنشاء JWT<br/>مع user + role]
        B1 --> C1[كتابة Cookie]
        C1 --> D1[طلب ← middleware<br/>فك تشفير JWT ← تحقق]
    end
    subgraph نموذج Database
        A2[تسجيل الدخول] --> B2[إنشاء Session<br/>كتابة في DB]
        B2 --> C2[Session ID ← Cookie]
        C2 --> D2[طلب ← middleware<br/>بحث في DB ← تحقق]
    end

    style A1 fill:#d4edda
    style A2 fill:#cce5ff

(2) تكوين Database Session

TS
// auth.ts — Database Session + Prisma
import NextAuth from 'next-auth'
import { PrismaAdapter } from '@auth/prisma-adapter'
import { prisma } from '@/lib/prisma'

export const { handlers, signIn, signOut, auth } = NextAuth({
  adapter: PrismaAdapter(prisma),
  session: { strategy: 'database' },
  providers: [GitHub, Google]
})

▶ مثال: استرجاع Session وعرض معلومات المستخدم

المخرجات:

TEXT 📖 للعرض فقط
تم تنفيذ وحدة TypeScript بنجاح.
TSX
// app/dashboard/page.tsx — استرجاع Session من جانب الخادم
import { auth } from '@/auth'

export default async function DashboardPage() {
  const session = await auth()

  if (!session?.user) return <p>يرجى تسجيل الدخول أولاً</p>

  return (
    <div>
      <h1>مرحبًا بعودتك، {session.user.name}</h1>
      <p>البريد الإلكتروني: {session.user.email}</p>
      <p>الدور: {session.user.role}</p>
      <img src={session.user.image!} alt="صورة رمزية" width={48} height={48} />
    </div>
  )
}
💻 المخرجات:

TEXT 📖 للعرض فقط
<h1>مرحبًا بعودتك، Alice</h1>
<p>البريد الإلكتروني: alice@taskflow.io</p>
<p>الدور: admin</p>

المخرجات:

TEXT 📖 للعرض فقط
<h1>Welcome back, Alice</h1>
<p>Email: alice@taskflow.io</p>
<p>Role: admin</p>


5. حماية التوجيه عبر Middleware

(1) وضع تكوين Matcher

يعترض middleware.ts الطلب قبل وصوله إلى الصفحة للتحقق من صلاحية session:

100%
graph TB
    A[طلب المستخدم /dashboard/*] --> B{middleware.ts}
    B -->|Session صالحة| C[مرور ← page.tsx]
    B -->|لا توجد Session| D[إعادة توجيه /login]
    D --> E{مسارات عامة؟}
    E -->|/api/auth/*| F[مرور]
    E -->|/_next/*| F
    E -->|/favicon.ico| F

    style B fill:#fff3cd
    style C fill:#d4edda
    style D fill:#f8d7da
نمط Matcher المسار المطابق الوصف
/dashboard/:path* /dashboard/* حماية لوحة التحكم
/api/projects/:path* /api/projects/* حماية API
`/((?!auth _next favicon).*)`

(2) تنفيذ Middleware كامل

TS
// middleware.ts
import { auth } from '@/auth'
import { NextResponse } from 'next/server'

export default auth((req) => {
  const { pathname } = req.nextUrl
  const isLoggedIn = !!req.auth
  const isPublicPath = pathname.startsWith('/login') ||
    pathname.startsWith('/register') ||
    pathname.startsWith('/api/auth')

  if (!isLoggedIn && !isPublicPath) {
    return NextResponse.redirect(new URL('/login', req.url))
  }

  // RBAC: منع غير admin من زيارة /admin
  if (pathname.startsWith('/admin') && req.auth?.user?.role !== 'admin') {
    return NextResponse.redirect(new URL('/dashboard', req.url))
  }

  return NextResponse.next()
})

export const config = {
  matcher: ['/((?!_next/static|_next/image|favicon.ico|images).*)']
}

▶ مثال: إضافة حماية المصادقة إلى مسار API

المخرجات:

TEXT 📖 للعرض فقط
يعترض Middleware الطلبات ويعيد توجيهها بناءً على الشروط.
TS
// app/api/projects/route.ts — API محمي
import { getToken } from 'next-auth/jwt'
import { NextRequest, NextResponse } from 'next/server'

export async function GET(req: NextRequest) {
  const token = await getToken({ req })

  if (!token) {
    return NextResponse.json({ error: 'غير مسجل الدخول' }, { status: 401 })
  }

  // token.role من استدعاء JWT
  if (token.role !== 'admin' && token.role !== 'editor') {
    return NextResponse.json({ error: 'صلاحيات غير كافية' }, { status: 403 })
  }

  return NextResponse.json({ projects: [{ id: 1, name: 'TaskFlow' }] })
}

المخرجات:

TEXT 📖 للعرض فقط
GET app/api/projects/route.ts ← يتحقق من token المصادقة، ويعيد البيانات المحمية كـ JSON.
🔥 خطأ شائع: getToken() يتطلب تعيين متغير البيئة AUTH_SECRET؛ وإلا فسيعيد null.



6. المصادقة الخارجية Clerk

(1) مقارنة ميزات Clerk

الميزة Auth.js Clerk
تعقيد التثبيت متوسط (يتطلب تكوين Provider) منخفض (npm + متغيرات بيئة)
مكونات واجهة المستخدم صفحة تسجيل دخول مخصصة <SignIn /> / <SignUp /> جاهزة للاستخدام
المصادقة متعددة العوامل يتطلب دمجًا يدويًا دعم مدمج
حدود الطبقة المجانية لا توجد 5,000 MAU (مجاني)
تخصيص واجهة المستخدم حرية كاملة قيود كثيرة

(2) خطوات دمج Clerk

BASH
npm install @clerk/nextjs
# في .env.local، أضف:
# NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=...
# CLERK_SECRET_KEY=...
TSX
// app/layout.tsx — ClerkProvider يغلف التخطيط الجذر
import { ClerkProvider } from '@clerk/nextjs'

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <ClerkProvider>
      <html lang="ar">
        <body>{children}</body>
      </html>
    </ClerkProvider>
  )
}

▶ مثال: صفحة تسجيل دخول Clerk + صفحة محمية

المخرجات:

TEXT 📖 للعرض فقط
يعرض واجهة مستخدم مكون RootLayout.
TSX
// app/page.tsx — صفحة تسجيل الدخول
import { SignIn, SignedIn, SignedOut, UserButton } from '@clerk/nextjs'

export default function HomePage() {
  return (
    <div>
      <SignedOut>
        <SignIn routing="hash" />
      </SignedOut>
      <SignedIn>
        <div>
          <UserButton afterSignOutUrl="/" />
          <h1>مرحبًا بك في TaskFlow</h1>
        </div>
      </SignedIn>
    </div>
  )
}

المخرجات:

TEXT 📖 للعرض فقط
يعرض: مرحبًا بك في TaskFlow
النص المرئي: مرحبًا بك في TaskFlow
TSX
// app/dashboard/page.tsx — حماية Clerk auth()
import { auth } from '@clerk/nextjs/server'
import { redirect } from 'next/navigation'

export default async function DashboardPage() {
  const { userId } = auth()

  if (!userId) redirect('/sign-in')

  return <div>لوحة التحكم — UserID: {userId}</div>
}
💡 نصيحة: دالة auth() من Clerk هي دالة من جانب الخادم؛ لا تحتاج إلى 'use client' عند استخدامها في Server Component.

(3) Clerk Middleware

TS
// middleware.ts
import { clerkMiddleware } from '@clerk/nextjs/server'

export default clerkMiddleware()

export const config = {
  matcher: ['/((?!_next|sign-in|sign-up|favicon.ico).*)']
}


7. RBAC: التحكم في الوصول القائم على الأدوار

(1) تصميم نموذج الأدوار

100%
graph TB
    A[المستخدم] --> B{الدور}
    B --> C[admin<br/>وصول كامل]
    B --> D[editor<br/>قراءة وكتابة المشاريع]
    B --> E[viewer<br/>للقراءة فقط]

    C --> F[إنشاء/حذف العناصر]
    C --> G[إدارة الفريق]
    C --> H[تغيير الإعدادات]
    D --> I[تحرير المهام]
    D --> J[إضافة تعليق]
    E --> K[عرض لوحة التحكم]
    E --> L[قراءة المستندات]

    style C fill:#d4edda
    style D fill:#cce5ff
    style E fill:#f8d7da
الدور مستوى الصلاحية الصفحات المتاحة
admin 100 الكل (بما في ذلك /admin)
editor 50 /dashboard، /projects (قابل للتحرير)
viewer 20 /dashboard (للقراءة فقط)

▶ مثال: مكون فحص صلاحيات RBAC

المخرجات:

TEXT 📖 للعرض فقط
مخطط: المستخدم؛ admin وصول كامل؛ editor قراءة وكتابة المشاريع؛ viewer للقراءة فقط؛ إنشاء/حذف العناصر؛ إدارة الفريق.
TSX
// components/PermissionGuard.tsx
import { auth } from '@/auth'
import { redirect } from 'next/navigation'

type Role = 'admin' | 'editor' | 'viewer'

const roleHierarchy: Record<Role, number> = { admin: 100, editor: 50, viewer: 20 }

export async function PermissionGuard({
  children,
  minRole
}: {
  children: React.ReactNode
  minRole: Role
}) {
  const session = await auth()
  const userRole = (session?.user?.role as Role) || 'viewer'

  if (roleHierarchy[userRole] < roleHierarchy[minRole]) {
    redirect('/dashboard')
  }

  return <>{children}</>
}

المخرجات:

TEXT 📖 للعرض فقط
يعرض واجهة مستخدم مكون PermissionGuard.
TSX
// app/admin/page.tsx — استخدام PermissionGuard
import { PermissionGuard } from '@/components/PermissionGuard'

export default function AdminPage() {
  return (
    <PermissionGuard minRole="admin">
      <h1>لوحة الإدارة</h1>
      <p>هذه الصفحة لا يمكن رؤيتها إلا من قبل admin.</p>
    </PermissionGuard>
  )
}


8. مثال كامل: تنفيذ شامل لمصادقة متعددة المزودين + RBAC

TSX
// app/dashboard/layout.tsx — تخطيط محمي + RBAC
import { auth } from '@/auth'
import { redirect } from 'next/navigation'
import { PermissionGuard } from '@/components/PermissionGuard'

export default async function DashboardLayout({
  children,
  analytics,
  team
}: {
  children: React.ReactNode
  analytics: React.ReactNode
  team: React.ReactNode
}) {
  const session = await auth()

  if (!session) redirect('/login')

  const user = session.user!

  return (
    <div>
      <header>
        <h1>TaskFlow</h1>
        <p>مرحبًا، {user.name} ({user.role})</p>
        <nav>
          <a href="/dashboard">نظرة عامة</a>
          {user.role === 'admin' && <a href="/admin">الإدارة</a>}
          <a href="/api/auth/signout">خروج</a>
        </nav>
      </header>
      <div style={{ display: 'flex', gap: '2rem' }}>
        <main>{children}</main>
        <aside>
          {analytics}
          <PermissionGuard minRole="editor">{team}</PermissionGuard>
        </aside>
      </div>
    </div>
  )
}
TS
// app/api/projects/[id]/route.ts — حماية API كاملة
import { getToken } from 'next-auth/jwt'
import { NextRequest, NextResponse } from 'next/server'

const roles = { admin: 100, editor: 50, viewer: 20 }

export async function DELETE(req: NextRequest, { params }: { params: Promise<{ id: string }> }) {
  const token = await getToken({ req })
  const { id } = await params

  if (!token) return NextResponse.json({ error: 'غير مصرح' }, { status: 401 })
  if (roles[token.role as keyof typeof roles] < 50) {
    return NextResponse.json({ error: 'محظور' }, { status: 403 })
  }

  // حذف العنصر فعليًا
  return NextResponse.json({ id, deleted: true })
}
💻 المخرجات (طلب DELETE بدون تسجيل دخول):

JSON
{ "error": "غير مصرح" }
💻 المخرجات (طلب DELETE من دور "viewer"):

JSON
{ "error": "محظور" }

❓ أسئلة شائعة

س ما الفرق بين Auth.js v4 (NextAuth) و v5؟
ج يستخدم v5 نمط Route Handler في App Router (app/api/auth/[...nextauth]/route.ts)، ويدعم استرجاع session مباشرة في Server Components عبر auth()، ولم يعد بحاجة إلى getSession() أو SessionProvider.
س أيهما أختار: JWT session أم database session؟
ج للتطبيقات الصغيرة أو التي لا تحتاج إلى إلغاء فوري للجلسات، اختر JWT (بدون استعلامات DB). للتطبيقات المؤسسية التي تحتاج إلى تسجيل خروج فوري للمستخدم أو إدارة أجهزة متعددة، اختر database session. في وضع JWT، يجب أن ينتظر إلغاء session حتى انتهاء صلاحية JWT (30 يومًا افتراضيًا).
س متى أختار Clerk ومتى أختار Auth.js؟
ج إذا كنت بحاجة إلى إطلاق سريع ولا تريد بناء واجهة مستخدم بنفسك ← Clerk (دمج في 30 دقيقة). إذا كنت بحاجة إلى واجهة مستخدم قابلة للتخصيص بالكامل وتريد بناء نظام حسابات خاص بك ← Auth.js. الطبقة المجانية من Clerk محدودة بـ 5,000 MAU؛ وما فوق ذلك بسعر €25/شهريًا.
س ما الفرق بين auth() و getToken() في Middleware؟
ج auth() هي دالة مغلفة من Auth.js v5 تعيد كائن Session كامل. getToken() من next-auth/jwt تحلل فقط رموز JWT، وتقدم أداءً أفضل. نوصي بـ auth() لبساطتها في middleware؛ أما لمسارات API فنوصي بـ getToken() لخفتها.
س كيف أؤمن الصفحات المصدرة بشكل ثابت؟
ج المواقع الثابتة المبنية بـ output: 'export' لا يمكنها استخدام middleware (الذي يتطلب بيئة تشغيل Node.js). في هذه الحالة، استخدم hook useSession() في مكون عميل للتحقق من حالة تسجيل الدخول، أو استخدم العرض الشرطي <SignedIn> / <SignedOut> من Clerk.

📖 ملخص


📝 تمارين

  1. تمرين أساسي (⭐): أنشئ مشروع Next.js جديدًا، وادمج مزود GitHub من Auth.js v5، واعرض الصورة الرمزية للمستخدم والبريد الإلكتروني على صفحة /dashboard.

  2. تمرين متقدم (⭐⭐): أضف منطق RBAC إلى middleware: يمكن لـ admin الوصول إلى /admin/*، ويمكن لـ editor الوصول إلى واجهة تحرير /projects/*؛ وأعد صفحة 403 إذا حاول أي دور آخر الوصول إلى هذه الموارد.

  3. تمرين تحدي (⭐⭐⭐): دمج Auth.js (مزود Credentials) مع Prisma: يقوم المستخدم بإدخال البريد الإلكتروني وكلمة المرور في صفحة تسجيل الدخول ← authorize يستعلم قاعدة البيانات للتحقق ← كتابة role و orgId في JWT ← Middleware يوجه إلى مساحة عمل المؤسسة المقابلة بناءً على orgId.

Web-Tutorial.com

فريق Web-Tutorial التقني

منصة دروس برمجية يديرها عدة مطورين. كل درس يتم كتابته ومراجعته بواسطة مطورين متخصصين في المجال. نعمل على ضمان دقة وموثوقية المحتوى — إذا لاحظت أي مشكلة، فيرجى إخبارنا.

100%