كيفية تنفيذ المصادقة في Next.js
فهم المصادقة أمر بالغ الأهمية لحماية بيانات تطبيقك. سيرشدك هذه الصفحة إلى ميزات React وNext.js التي يمكن استخدامها لتنفيذ المصادقة.
قبل البدء، من المفيد تقسيم العملية إلى ثلاثة مفاهيم:
- المصادقة: التحقق مما إذا كان المستخدم هو من يدعي أنه هو. يتطلب من المستخدم إثبات هويته بشيء يمتلكه، مثل اسم المستخدم وكلمة المرور.
- إدارة الجلسة: تتبع حالة مصادقة المستخدم عبر الطلبات.
- الترخيص: تحديد المسارات والبيانات التي يمكن للمستخدم الوصول إليها.
يظهر هذا الرسم التخطيطي تدفق المصادقة باستخدام ميزات React وNext.js:

تقدم الأمثلة في هذه الصفحة شرحًا أساسيًا لمصادقة اسم المستخدم وكلمة المرور لأغراض تعليمية. بينما يمكنك تنفيذ حل مصادقة مخصص، نوصي باستخدام مكتبة مصادقة لزيادة الأمان والبساطة. توفر هذه المكتبات حلولًا مدمجة للمصادقة وإدارة الجلسات والترخيص، بالإضافة إلى ميزات إضافية مثل تسجيلات الدخول الاجتماعية والمصادقة متعددة العوامل والتحكم في الوصول القائم على الأدوار. يمكنك العثور على قائمة في قسم مكتبات المصادقة.
المصادقة
إليك خطوات تنفيذ نموذج تسجيل و/أو تسجيل الدخول:
١. يقدم المستخدم بيانات الاعتماد الخاصة به من خلال نموذج. ٢. يرسل النموذج طلبًا يتم معالجته بواسطة مسار API. ٣. عند التحقق الناجح، تكتمل العملية، مما يشير إلى نجاح مصادقة المستخدم. ٤. إذا فشل التحقق، يتم عرض رسالة خطأ.
ضع في اعتبارك نموذج تسجيل الدخول حيث يمكن للمستخدمين إدخال بيانات الاعتماد الخاصة بهم:
import { FormEvent } from 'react'
import { useRouter } from 'next/router'
export default function LoginPage() {
const router = useRouter()
async function handleSubmit(event: FormEvent<HTMLFormElement>) {
event.preventDefault()
const formData = new FormData(event.currentTarget)
const email = formData.get('email')
const password = formData.get('password')
const response = await fetch('/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password }),
})
if (response.ok) {
router.push('/profile')
} else {
// معالجة الأخطاء
}
}
return (
<form onSubmit={handleSubmit}>
<input type="email" name="email" placeholder="البريد الإلكتروني" required />
<input type="password" name="password" placeholder="كلمة المرور" required />
<button type="submit">تسجيل الدخول</button>
</form>
)
}
import { FormEvent } from 'react'
import { useRouter } from 'next/router'
export default function LoginPage() {
const router = useRouter()
async function handleSubmit(event) {
event.preventDefault()
const formData = new FormData(event.currentTarget)
const email = formData.get('email')
const password = formData.get('password')
const response = await fetch('/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password }),
})
if (response.ok) {
router.push('/profile')
} else {
// معالجة الأخطاء
}
}
return (
<form onSubmit={handleSubmit}>
<input type="email" name="email" placeholder="البريد الإلكتروني" required />
<input type="password" name="password" placeholder="كلمة المرور" required />
<button type="submit">تسجيل الدخول</button>
</form>
)
}
يحتوي النموذج أعلاه على حقلين إدخال لجمع البريد الإلكتروني وكلمة المرور للمستخدم. عند الإرسال، يتم تشغيل وظيفة ترسل طلب POST إلى مسار API (/api/auth/login
).
يمكنك بعد ذلك استدعاء واجهة برمجة التطبيقات الخاصة بمزود المصادقة في مسار API لمعالجة المصادقة:
import type { NextApiRequest, NextApiResponse } from 'next'
import { signIn } from '@/auth'
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
try {
const { email, password } = req.body
await signIn('credentials', { email, password })
res.status(200).json({ success: true })
} catch (error) {
if (error.type === 'CredentialsSignin') {
res.status(401).json({ error: 'بيانات الاعتماد غير صالحة.' })
} else {
res.status(500).json({ error: 'حدث خطأ ما.' })
}
}
}
import { signIn } from '@/auth'
export default async function handler(req, res) {
try {
const { email, password } = req.body
await signIn('credentials', { email, password })
res.status(200).json({ success: true })
} catch (error) {
if (error.type === 'CredentialsSignin') {
res.status(401).json({ error: 'بيانات الاعتماد غير صالحة.' })
} else {
res.status(500).json({ error: 'حدث خطأ ما.' })
}
}
}
إدارة الجلسة
تضمن إدارة الجلسة الحفاظ على حالة مصادقة المستخدم عبر الطلبات. وهي تشمل إنشاء وتخزين وتحديث وحذف الجلسات أو الرموز (tokens).
هناك نوعان من الجلسات:
١. بدون حالة (Stateless): يتم تخزين بيانات الجلسة (أو الرمز) في ملفات تعريف الارتباط (cookies) للمتصفح. يتم إرسال ملف تعريف الارتباط مع كل طلب، مما يسمح بالتحقق من الجلسة على الخادم. هذه الطريقة أبسط، ولكن يمكن أن تكون أقل أمانًا إذا لم يتم تنفيذها بشكل صحيح. ٢. قاعدة البيانات (Database): يتم تخزين بيانات الجلسة في قاعدة بيانات، مع تلقي متصفح المستخدم فقط لمعرف الجلسة المشفر. هذه الطريقة أكثر أمانًا، ولكن يمكن أن تكون معقدة وتستخدم موارد أكثر على الخادم.
من الجيد معرفة: بينما يمكنك استخدام أي من الطريقتين، أو كليهما، نوصي باستخدام مكتبة إدارة جلسات مثل iron-session أو Jose.
الجلسات بدون حالة (Stateless)
تعيين وحذف ملفات تعريف الارتباط
يمكنك استخدام مسارات API لتعيين الجلسة كملف تعريف ارتباط على الخادم:
import { serialize } from 'cookie'
import type { NextApiRequest, NextApiResponse } from 'next'
import { encrypt } from '@/app/lib/session'
export default function handler(req: NextApiRequest, res: NextApiResponse) {
const sessionData = req.body
const encryptedSessionData = encrypt(sessionData)
const cookie = serialize('session', encryptedSessionData, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
maxAge: 60 * 60 * 24 * 7, // أسبوع واحد
path: '/',
})
res.setHeader('Set-Cookie', cookie)
res.status(200).json({ message: 'تم تعيين ملف تعريف الارتباط بنجاح!' })
}
import { serialize } from 'cookie'
import { encrypt } from '@/app/lib/session'
export default function handler(req, res) {
const sessionData = req.body
const encryptedSessionData = encrypt(sessionData)
const cookie = serialize('session', encryptedSessionData, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
maxAge: 60 * 60 * 24 * 7, // أسبوع واحد
path: '/',
})
res.setHeader('Set-Cookie', cookie)
res.status(200).json({ message: 'تم تعيين ملف تعريف الارتباط بنجاح!' })
}
جلسات قاعدة البيانات
لإنشاء وإدارة جلسات قاعدة البيانات، ستحتاج إلى اتباع هذه الخطوات:
- إنشاء جدول في قاعدة البيانات لتخزين بيانات الجلسة (أو التحقق مما إذا كانت مكتبة المصادقة الخاصة بك تتعامل مع هذا).
- تنفيذ وظائف لإدراج وتحديث وحذف الجلسات
- تشفير معرّف الجلسة قبل تخزينه في متصفح المستخدم، والتأكد من تزامن قاعدة البيانات وملف تعريف الارتباط (هذا اختياري، ولكنه موصى به للتحقق المتفائل من المصادقة في الوسيط).
إنشاء جلسة على الخادم:
import db from '../../lib/db'
import type { NextApiRequest, NextApiResponse } from 'next'
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
try {
const user = req.body
const sessionId = generateSessionId()
await db.insertSession({
sessionId,
userId: user.id,
createdAt: new Date(),
})
res.status(200).json({ sessionId })
} catch (error) {
res.status(500).json({ error: 'خطأ في الخادم الداخلي' })
}
}
import db from '../../lib/db'
export default async function handler(req, res) {
try {
const user = req.body
const sessionId = generateSessionId()
await db.insertSession({
sessionId,
userId: user.id,
createdAt: new Date(),
})
res.status(200).json({ sessionId })
} catch (error) {
res.status(500).json({ error: 'خطأ في الخادم الداخلي' })
}
}
التفويض
بعد مصادقة المستخدم وإنشاء جلسة، يمكنك تنفيذ التفويض للتحكم فيما يمكن للمستخدم الوصول إليه والقيام به داخل تطبيقك.
هناك نوعان رئيسيان من عمليات التحقق من التفويض:
- المتفائل: يتحقق مما إذا كان المستخدم مخولاً للوصول إلى مسار أو تنفيذ إجراء باستخدام بيانات الجلسة المخزنة في ملف تعريف الارتباط. هذه الفحوصات مفيدة للعمليات السريعة، مثل إظهار/إخفاء عناصر واجهة المستخدم أو إعادة توجيه المستخدمين بناءً على الأذونات أو الأدوار.
- الآمن: يتحقق مما إذا كان المستخدم مخولاً للوصول إلى مسار أو تنفيذ إجراء باستخدام بيانات الجلسة المخزنة في قاعدة البيانات. هذه الفحوصات أكثر أمانًا وتستخدم للعمليات التي تتطلب الوصول إلى بيانات حساسة أو إجراءات.
لكلا الحالتين، نوصي بما يلي:
- إنشاء طبقة وصول البيانات لمركزية منطق التفويض
- استخدام كائنات نقل البيانات (DTO) لإرجاع البيانات الضرورية فقط
- استخدام الوسيط اختياريًا لإجراء فحوصات متفائلة.
فحوصات متفائلة باستخدام الوسيط (اختياري)
هناك بعض الحالات التي قد ترغب في استخدام الوسيط وإعادة توجيه المستخدمين بناءً على الأذونات:
- لإجراء فحوصات متفائلة. نظرًا لأن الوسيط يعمل على كل مسار، فهو طريقة جيدة لمركزية منطق إعادة التوجيه وتصفية المستخدمين غير المصرح لهم مسبقًا.
- لحماية المسارات الثابتة التي تشارك البيانات بين المستخدمين (مثل المحتوى خلف جدار الدفع).
ومع ذلك، نظرًا لأن الوسيط يعمل على كل مسار، بما في ذلك المسارات المحملة مسبقًا، فمن المهم قراءة الجلسة فقط من ملف تعريف الارتباط (فحوصات متفائلة)، وتجنب فحوصات قاعدة البيانات لمنع مشكلات الأداء.
على سبيل المثال:
import { NextRequest, NextResponse } from 'next/server'
import { decrypt } from '@/app/lib/session'
import { cookies } from 'next/headers'
// 1. تحديد المسارات المحمية والعامة
const protectedRoutes = ['/dashboard']
const publicRoutes = ['/login', '/signup', '/']
export default async function middleware(req: NextRequest) {
// 2. التحقق مما إذا كان المسار الحالي محميًا أو عامًا
const path = req.nextUrl.pathname
const isProtectedRoute = protectedRoutes.includes(path)
const isPublicRoute = publicRoutes.includes(path)
// 3. فك تشفير الجلسة من ملف تعريف الارتباط
const cookie = (await cookies()).get('session')?.value
const session = await decrypt(cookie)
// 4. إعادة التوجيه إلى /login إذا لم يكن المستخدم مصادقًا عليه
if (isProtectedRoute && !session?.userId) {
return NextResponse.redirect(new URL('/login', req.nextUrl))
}
// 5. إعادة التوجيه إلى /dashboard إذا كان المستخدم مصادقًا عليه
if (
isPublicRoute &&
session?.userId &&
!req.nextUrl.pathname.startsWith('/dashboard')
) {
return NextResponse.redirect(new URL('/dashboard', req.nextUrl))
}
return NextResponse.next()
}
// المسارات التي لا يجب أن يعمل عليها الوسيط
export const config = {
matcher: ['/((?!api|_next/static|_next/image|.*\\.png$).*)'],
}
import { NextResponse } from 'next/server'
import { decrypt } from '@/app/lib/session'
import { cookies } from 'next/headers'
// 1. تحديد المسارات المحمية والعامة
const protectedRoutes = ['/dashboard']
const publicRoutes = ['/login', '/signup', '/']
export default async function middleware(req) {
// 2. التحقق مما إذا كان المسار الحالي محميًا أو عامًا
const path = req.nextUrl.pathname
const isProtectedRoute = protectedRoutes.includes(path)
const isPublicRoute = publicRoutes.includes(path)
// 3. فك تشفير الجلسة من ملف تعريف الارتباط
const cookie = (await cookies()).get('session')?.value
const session = await decrypt(cookie)
// 5. إعادة التوجيه إلى /login إذا لم يكن المستخدم مصادقًا عليه
if (isProtectedRoute && !session?.userId) {
return NextResponse.redirect(new URL('/login', req.nextUrl))
}
// 6. إعادة التوجيه إلى /dashboard إذا كان المستخدم مصادقًا عليه
if (
isPublicRoute &&
session?.userId &&
!req.nextUrl.pathname.startsWith('/dashboard')
) {
return NextResponse.redirect(new URL('/dashboard', req.nextUrl))
}
return NextResponse.next()
}
// المسارات التي لا يجب أن يعمل عليها الوسيط
export const config = {
matcher: ['/((?!api|_next/static|_next/image|.*\\.png$).*)'],
}
بينما يمكن أن يكون الوسيط مفيدًا للفحوصات الأولية، لا يجب أن يكون خط دفاعك الوحيد في حماية بياناتك. يجب إجراء غالبية فحوصات الأمان بالقرب من مصدر البيانات قدر الإمكان، راجع طبقة وصول البيانات لمزيد من المعلومات.
نصائح:
- في الوسيط، يمكنك أيضًا قراءة ملفات تعريف الارتباط باستخدام
req.cookies.get('session').value
.- يستخدم الوسيط وقت تشغيل الحافة، تحقق مما إذا كانت مكتبة المصادقة ومكتبة إدارة الجلسات متوافقة.
- يمكنك استخدام خاصية
matcher
في الوسيط لتحديد المسارات التي يجب أن يعمل عليها الوسيط. ومع ذلك، للمصادقة، يوصى بأن يعمل الوسيط على جميع المسارات.
إنشاء طبقة الوصول إلى البيانات (DAL)
حماية مسارات واجهة برمجة التطبيقات (API Routes)
مسارات واجهة برمجة التطبيقات في Next.js ضرورية للتعامل مع منطق الخادم وإدارة البيانات. من الضروري تأمين هذه المسارات لضمان أن المستخدمين المصرح لهم فقط يمكنهم الوصول إلى وظائف محددة. يتضمن هذا عادةً التحقق من حالة مصادقة المستخدم وأذوناته القائمة على الأدوار.
إليك مثالًا لتأمين مسار واجهة برمجة التطبيقات:
import { NextApiRequest, NextApiResponse } from 'next'
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
const session = await getSession(req)
// Check if the user is authenticated
if (!session) {
res.status(401).json({
error: 'User is not authenticated',
})
return
}
// Check if the user has the 'admin' role
if (session.user.role !== 'admin') {
res.status(401).json({
error: 'Unauthorized access: User does not have admin privileges.',
})
return
}
// Proceed with the route for authorized users
// ... implementation of the API Route
}
export default async function handler(req, res) {
const session = await getSession(req)
// Check if the user is authenticated
if (!session) {
res.status(401).json({
error: 'User is not authenticated',
})
return
}
// Check if the user has the 'admin' role
if (session.user.role !== 'admin') {
res.status(401).json({
error: 'Unauthorized access: User does not have admin privileges.',
})
return
}
// Proceed with the route for authorized users
// ... implementation of the API Route
}
يوضح هذا المثال مسار واجهة برمجة التطبيقات بفحص أمان من مستويين للمصادقة والتفويض. أولاً يتحقق من وجود جلسة نشطة، ثم يتحقق مما إذا كان المستخدم المسجل دخوله هو 'admin'. يضمن هذا النهج وصولاً آمنًا، مقصورًا على المستخدمين المصادق عليهم والمصرح لهم، مع الحفاظ على أمان قوي لمعالجة الطلبات.
موارد
الآن بعد أن تعلمت عن المصادقة في Next.js، إليك مكتبات وموارد متوافقة مع Next.js لمساعدتك في تنفيذ المصادقة الآمنة وإدارة الجلسات:
مكتبات المصادقة
مكتبات إدارة الجلسات
قراءة إضافية
لمواصلة التعلم عن المصادقة والأمان، تحقق من الموارد التالية: