الكتابات/blog/2026/07
Blog6 يوليو 2026·6 دقيقة

تأمين Server Actions في Next.js: منع الوصول غير المصرح به في 2026

تكشف Server Actions في Next.js عن نقاط نهاية HTTP POST يمكن لأي عميل استدعاؤها. تعلّم كيف تضيف فحوصات المصادقة وحماية Middleware وأدوات التدقيق لتأمين تطبيقك.

تُعدّ Server Actions في Next.js من أقوى ميزات الإطار — إذ تتيح لك كتابة كود من جانب الخادم مباشرةً في مكونات React، مع إنشاء نقاط نهاية POST تلقائياً. لكن هذه السهولة تنطوي على ثغرة أمنية خفية قد تكشف بيانات حساسة وتُمكّن المهاجمين من الاستيلاء على الحسابات.

المشكلة: Server Actions نقاط نهاية POST عامة

عند تعريف Server Action، يُنشئ Next.js نقطة نهاية POST يمكن لأي عميل استدعاؤها — وليس واجهتك فقط. هذا يعني أن مستخدماً غير مصادق يمكنه استدعاء إجراءات تعديل البيانات مباشرةً عبر curl أو fetch، وتجاوز حراسة المصادقة من جانب العميل كلياً، والوصول إلى بيانات لا يحق له الاطلاع عليها أو تعديلها.

// هذا الإجراء متاح لأي شخص
'use server'
 
export async function deletePost(postId: string) {
  await db.posts.delete({ where: { id: postId } }) // لا يوجد فحص مصادقة!
}

عنوان URL لنقطة النهاية مشتق من hash الإجراء، لكنه متاح لأي عميل HTTP — وليس واجهتك الأمامية فقط.

لماذا يحدث هذا؟

لا تُضيف Server Actions في Next.js مصادقة بشكل افتراضي. يفترض المطورون أحياناً أنه لكون الإجراء "على الخادم" فهو محمي — لكن أي عميل HTTP يمكنه استدعاء نقاط النهاية هذه مباشرةً. يعاملها الإطار كدوال من جانب الخادم بينما يكشفها كمسارات يمكن الوصول إليها عبر الشبكة.

الحل الأول: فحوصات المصادقة المباشرة

أبسط حماية هي فحص المصادقة في بداية كل إجراء:

'use server'
 
import { getServerSession } from 'next-auth'
import { authOptions } from '@/lib/auth'
 
export async function deletePost(postId: string) {
  const session = await getServerSession(authOptions)
 
  if (!session?.user) {
    throw new Error('غير مصرح')
  }
 
  // تحقق من الملكية أيضاً
  const post = await db.posts.findUnique({ where: { id: postId } })
  if (post?.authorId !== session.user.id) {
    throw new Error('محظور')
  }
 
  await db.posts.delete({ where: { id: postId } })
}

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

الحل الثاني: دالة تغليف للمصادقة

أنشئ مُغلِّف withAuth يفرض المصادقة لأي إجراء:

// lib/with-auth.ts
import { getServerSession } from 'next-auth'
import { authOptions } from '@/lib/auth'
 
type ActionFn<T, R> = (user: User, input: T) => Promise<R>
 
export async function withAuth<T, R>(
  action: ActionFn<T, R>,
  input: T
): Promise<R> {
  const session = await getServerSession(authOptions)
 
  if (!session?.user) {
    throw new Error('غير مصرح: يرجى تسجيل الدخول للمتابعة.')
  }
 
  return action(session.user, input)
}

يصبح الاستخدام نظيفاً ومتسقاً:

'use server'
 
import { withAuth } from '@/lib/with-auth'
 
export async function deletePost(postId: string) {
  return withAuth(async (user) => {
    const post = await db.posts.findUnique({ where: { id: postId } })
    if (post?.authorId !== user.id) throw new Error('محظور')
    return db.posts.delete({ where: { id: postId } })
  }, postId)
}

الحل الثالث: الحماية عبر Middleware

يوفر Middleware في Next.js طبقة حماية مركزية لشرائح المسار بأكملها:

// middleware.ts
import { withAuth } from 'next-auth/middleware'
 
export default withAuth({
  pages: {
    signIn: '/auth/signin',
  },
})
 
export const config = {
  matcher: ['/dashboard/:path*'],
}

يُعدّ Middleware ممتازاً لبوابات مستوى المسار، لكنه لا يُغني عن فحوصات المصادقة لكل إجراء في العمليات الحساسة، إذ يعمل Middleware قبل الإجراء ولا يستطيع فرض الأذونات على مستوى الكائن.

اعتبارات CSRF

يتضمن Next.js 14 وما بعده حماية مدمجة من هجمات CSRF لـ Server Actions من خلال التحقق من رأس Origin. ومع ذلك، تنطبق هذه الحماية فقط على الاستدعاءات من نفس الأصل ولا تُعوّض المصادقة. بالنسبة للتطبيقات التي تستخدم جلسات قائمة على ملفات تعريف الارتباط، يوفر إضافة رموز CSRF صريحة إلى التعديلات الحساسة طبقة دفاع إضافية.

تدقيق تطبيقك مع Shipcheck

تقوم أداة @shipcheck/cli بفحص مشروع Next.js الخاص بك بحثاً عن Server Actions غير محمية:

npx @shipcheck/cli

تُحدّد الأداة ملفات Server Action التي تفتقر إلى فحوصات المصادقة وتُنتج تقريراً بالثغرات المحتملة. تشغيلها كجزء من قائمة التحقق قبل النشر يُصحح الثغرات قبل وصولها إلى الإنتاج.

تحديد معدل الطلبات للإجراءات الحساسة

المصادقة وحدها لا تكفي للإجراءات عالية الخطورة. أضف تحديداً لمعدل الطلبات لمنع هجمات القوة الغاشمة أو الإساءة:

'use server'
 
import { Ratelimit } from '@upstash/ratelimit'
import { Redis } from '@upstash/redis'
 
const ratelimit = new Ratelimit({
  redis: Redis.fromEnv(),
  limiter: Ratelimit.slidingWindow(5, '1 m'),
})
 
export async function requestPasswordReset(email: string) {
  const session = await getServerSession(authOptions)
  const identifier = session?.user?.id ?? email
 
  const result = await ratelimit.limit(identifier)
  if (!result.success) {
    throw new Error('طلبات كثيرة جداً. يرجى المحاولة لاحقاً.')
  }
 
  // المتابعة بمنطق إعادة التعيين
}

قائمة التحقق الأمنية للإنتاج

قبل نشر أي تطبيق Next.js يستخدم Server Actions:

  • كل إجراء يقرأ أو يُعدّل البيانات يُجري فحص مصادقة
  • تفويض على مستوى الكائن يضمن أن المستخدمين يؤثرون فقط في مواردهم الخاصة
  • تحديد معدل الطلبات مُطبَّق على الإجراءات الحساسة
  • رسائل الخطأ لا تكشف تفاصيل داخلية
  • محاولات المصادقة الفاشلة مُسجَّلة للمراقبة
  • تم تشغيل @shipcheck/cli ومعالجة النتائج

العقلية الصحيحة

عامل Server Actions بنفس طريقة تعاملك مع مسارات REST API — لأن هذا بالضبط ما هي عليه على مستوى الشبكة. تجربة المطور في وضع منطق الخادم جنباً إلى جنب مع مكوناتك قوية حقاً، لكنها لا تجعل ذلك الكود أقل انكشافاً على الإنترنت العام.

المصادقة الصريحة على مستوى الإجراء، مدمجةً مع Middleware لبوابات مستوى المسار وتحديد معدل الطلبات للعمليات عالية الخطورة، تمنحك دفاعاً متعدد الطبقات يحمي مستخدميك بغض النظر عن كيفية محاولة المهاجم الوصول إلى خادمك.

النمط هو نفسه الذي حافظ على أمان REST APIs لسنوات: لا تثق أبداً في الطلب، تحقق دائماً من المُرسِل، واحدد الأذونات وفقاً لما يُسمح فعلياً للمستخدم المصادق بالقيام به.