سياسة أمان المحتوى (CSP)

سياسة أمان المحتوى (CSP) مهمة لحماية تطبيق Next.js الخاص بك من تهديدات أمنية مختلفة مثل البرمجة النصية عبر المواقع (XSS)، والتصيد بالنقر (clickjacking)، وهجمات حقن التعليمات البرمجية الأخرى.

باستخدام CSP، يمكن للمطورين تحديد المصادر المسموح بها للمحتوى، والنصوص البرمجية، وملفات الأنماط، والصور، والخطوط، والكائنات، والوسائط (الصوت، الفيديو)، والإطارات المضمنة (iframes)، والمزيد.

أمثلة

القيم العشوائية لمرة واحدة (Nonces)

القيمة العشوائية لمرة واحدة (nonce) هي سلسلة فريدة وعشوائية من الأحرف يتم إنشاؤها لاستخدام لمرة واحدة. تُستخدم مع CSP للسماح بشكل انتقائي بتنفيذ نصوص برمجية مضمنة معينة أو أنماط، متجاوزة توجيهات CSP الصارمة.

لماذا تستخدم nonce؟

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

إضافة nonce باستخدام Middleware

البرنامج الوسيط (Middleware) يتيح لك إضافة رؤوس وإنشاء قيم عشوائية لمرة واحدة قبل عرض الصفحة.

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

على سبيل المثال:

import { NextRequest, NextResponse } from 'next/server'

export function middleware(request: NextRequest) {
  const nonce = Buffer.from(crypto.randomUUID()).toString('base64')
  const cspHeader = `
    default-src 'self';
    script-src 'self' 'nonce-${nonce}' 'strict-dynamic';
    style-src 'self' 'nonce-${nonce}';
    img-src 'self' blob: data:;
    font-src 'self';
    object-src 'none';
    base-uri 'self';
    form-action 'self';
    frame-ancestors 'none';
    block-all-mixed-content;
    upgrade-insecure-requests;
`

  const requestHeaders = new Headers(request.headers)
  requestHeaders.set('x-nonce', nonce)
  requestHeaders.set(
    'Content-Security-Policy',
    // استبدال الأحرف الجديدة والمسافات
    cspHeader.replace(/\s{2,}/g, ' ').trim()
  )

  return NextResponse.next({
    headers: requestHeaders,
    request: {
      headers: requestHeaders,
    },
  })
}
import { NextResponse } from 'next/server'

export function middleware(request) {
  const nonce = Buffer.from(crypto.randomUUID()).toString('base64')
  const cspHeader = `
    default-src 'self';
    script-src 'self' 'nonce-${nonce}' 'strict-dynamic';
    style-src 'self' 'nonce-${nonce}';
    img-src 'self' blob: data:;
    font-src 'self';
    object-src 'none';
    base-uri 'self';
    form-action 'self';
    frame-ancestors 'none';
    block-all-mixed-content;
    upgrade-insecure-requests;
`

  const requestHeaders = new Headers(request.headers)
  requestHeaders.set('x-nonce', nonce)
  requestHeaders.set(
    'Content-Security-Policy',
    // استبدال الأحرف الجديدة والمسافات
    cspHeader.replace(/\s{2,}/g, ' ').trim()
  )

  return NextResponse.next({
    headers: requestHeaders,
    request: {
      headers: requestHeaders,
    },
  })
}

بشكل افتراضي، يعمل البرنامج الوسيط على جميع الطلبات. يمكنك تصفية البرنامج الوسيط ليعمل على مسارات محددة باستخدام matcher.

نوصي بتجاهل مطابقة الجلب المسبق (من next/link) والموارد الثابتة التي لا تحتاج إلى رأس CSP.

export const config = {
  matcher: [
    /*
     * مطابقة جميع مسارات الطلبات باستثناء تلك التي تبدأ بـ:
     * - api (مسارات API)
     * - _next/static (ملفات ثابتة)
     * - _next/image (ملفات تحسين الصور)
     * - favicon.ico (ملف الأيقونة)
     */
    {
      source: '/((?!api|_next/static|_next/image|favicon.ico).*)',
      missing: [
        { type: 'header', key: 'next-router-prefetch' },
        { type: 'header', key: 'purpose', value: 'prefetch' },
      ],
    },
  ],
}
export const config = {
  matcher: [
    /*
     * مطابقة جميع مسارات الطلبات باستثناء تلك التي تبدأ بـ:
     * - api (مسارات API)
     * - _next/static (ملفات ثابتة)
     * - _next/image (ملفات تحسين الصور)
     * - favicon.ico (ملف الأيقونة)
     */
    {
      source: '/((?!api|_next/static|_next/image|favicon.ico).*)',
      missing: [
        { type: 'header', key: 'next-router-prefetch' },
        { type: 'header', key: 'purpose', value: 'prefetch' },
      ],
    },
  ],
}

قراءة القيمة العشوائية لمرة واحدة

يمكنك الآن قراءة القيمة العشوائية لمرة واحدة من مكون الخادم (Server Component) باستخدام headers:

import { headers } from 'next/headers'
import Script from 'next/script'

export default function Page() {
  const nonce = headers().get('x-nonce')

  return (
    <Script
      src="https://www.googletagmanager.com/gtag/js"
      strategy="afterInteractive"
      nonce={nonce}
    />
  )
}
import { headers } from 'next/headers'
import Script from 'next/script'

export default function Page() {
  const nonce = headers().get('x-nonce')

  return (
    <Script
      src="https://www.googletagmanager.com/gtag/js"
      strategy="afterInteractive"
      nonce={nonce}
    />
  )
}

سجل الإصدارات

نوصي باستخدام إصدار v13.4.20+ من Next.js للتعامل مع القيم العشوائية لمرة واحدة وتطبيقها بشكل صحيح.