Next.js: المصادقة والتفويض
آخر تحديث: 2026-08-26
إضافة المصادقة إلى تطبيق full-stack يشبه تركيب نظام تحكم في الوصول في مبنى إداري — تحتاج إلى تحكم دقيق في من يمكنه الدخول والطوابق التي يمكنه الوصول إليها.
1. ما ستتعلمه
- دمج Auth.js v5 (NextAuth) مع مزودي Credentials / GitHub / Google متعددين
- اختيار وتكوين استراتيجيات JWT و Database Session
- حماية التوجيه عبر Middleware واستبعاد المسارات العامة باستخدام
matcher - دمج المصادقة الخارجية Clerk (
<SignIn />/<SignUp />/ مساعدauth()) - RBAC: التحكم في الوصول القائم على الأدوار (admin / editor / viewer)
- مصادقة
getToken()لمسارات API المحمية
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 كبديل بدون تكوين.
// 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 التكامل مع مصادر مصادقة متعددة عبر واجهة موحدة:
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) التثبيت والتهيئة
npm install next-auth@beta
npx auth secret # إنشاء AUTH_SECRET
// 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
المخرجات:
تم تنفيذ كود TypeScript بنجاح.
// 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
}
}
})
المخرجات:
تم تحميل تكوين 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 |
| النطاق المناسب | تطبيقات صغيرة ومتوسطة | تطبيقات مؤسسية كبيرة |
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
// 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 وعرض معلومات المستخدم
المخرجات:
تم تنفيذ وحدة TypeScript بنجاح.
// 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>
)
}
<h1>مرحبًا بعودتك، Alice</h1>
<p>البريد الإلكتروني: alice@taskflow.io</p>
<p>الدور: admin</p>
المخرجات:
<h1>Welcome back, Alice</h1>
<p>Email: alice@taskflow.io</p>
<p>Role: admin</p>
5. حماية التوجيه عبر Middleware
(1) وضع تكوين Matcher
يعترض middleware.ts الطلب قبل وصوله إلى الصفحة للتحقق من صلاحية session:
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 كامل
// 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
المخرجات:
يعترض Middleware الطلبات ويعيد توجيهها بناءً على الشروط.
// 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' }] })
}
المخرجات:
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
npm install @clerk/nextjs
# في .env.local، أضف:
# NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=...
# CLERK_SECRET_KEY=...
// 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 + صفحة محمية
المخرجات:
يعرض واجهة مستخدم مكون RootLayout.
// 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>
)
}
المخرجات:
يعرض: مرحبًا بك في TaskFlow
النص المرئي: مرحبًا بك في TaskFlow
// 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
// 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) تصميم نموذج الأدوار
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
المخرجات:
مخطط: المستخدم؛ admin وصول كامل؛ editor قراءة وكتابة المشاريع؛ viewer للقراءة فقط؛ إنشاء/حذف العناصر؛ إدارة الفريق.
// 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}</>
}
المخرجات:
يعرض واجهة مستخدم مكون PermissionGuard.
// 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
// 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>
)
}
// 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 })
}
{ "error": "غير مصرح" }
{ "error": "محظور" }
❓ أسئلة شائعة
app/api/auth/[...nextauth]/route.ts)، ويدعم استرجاع session مباشرة في Server Components عبر auth()، ولم يعد بحاجة إلى getSession() أو SessionProvider.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.📖 ملخص
- يستخدم Auth.js v5 نمط Route Handler؛ ويمكن لـ
auth()استرجاع session مباشرة داخل Server Component - يدعم Provider مصادر متعددة مثل Credentials و GitHub و Google، ويتيح حقن بيانات مخصصة عبر آلية callback
- جلسات JWT بدون استعلامات قاعدة بيانات مناسبة للتطبيقات الصغيرة، بينما جلسات database مع دعم الإلغاء الفوري مناسبة للمؤسسات
- يستخدم Middleware تكوين
matcherلحماية المسارات ويستبعد الموارد العامة عبر المطابقة العكسية - يوفر Clerk مكونات واجهة مستخدم جاهزة مثالية للنماذج الأولية السريعة، مع طبقة مجانية تصل إلى 5,000 MAU
- ينفذ RBAC التحكم في الصلاحيات عبر مقارنة قيم الأدوار ويستخدم بشكل تصريحي مع مكون PermissionGuard
📝 تمارين
-
تمرين أساسي (⭐): أنشئ مشروع Next.js جديدًا، وادمج مزود GitHub من Auth.js v5، واعرض الصورة الرمزية للمستخدم والبريد الإلكتروني على صفحة
/dashboard. -
تمرين متقدم (⭐⭐): أضف منطق RBAC إلى middleware: يمكن لـ
adminالوصول إلى/admin/*، ويمكن لـeditorالوصول إلى واجهة تحرير/projects/*؛ وأعد صفحة 403 إذا حاول أي دور آخر الوصول إلى هذه الموارد. -
تمرين تحدي (⭐⭐⭐): دمج Auth.js (مزود Credentials) مع Prisma: يقوم المستخدم بإدخال البريد الإلكتروني وكلمة المرور في صفحة تسجيل الدخول ←
authorizeيستعلم قاعدة البيانات للتحقق ← كتابةroleوorgIdفي JWT ← Middleware يوجه إلى مساحة عمل المؤسسة المقابلة بناءً علىorgId.