İçeriğe geç
nextjs

Next.js Security Headers ve CSP Yapılandırması

Tarık Tunç
Next.js Security Headers ve CSP Yapılandırması

Next.js Security Headers ve CSP Yapılandırması

Web uygulamalarının güvenliği, sadece kod kalitesiyle değil, doğru HTTP başlık yapılandırmasıyla da sağlanır. Next.js projelerinde Security Headers ve Content Security Policy (CSP) ayarları, XSS, clickjacking ve veri sızıntısı gibi yaygın saldırılara karşı ilk savunma hattını oluşturur. Bu rehberde Next.js'te güvenlik başlıklarını adım adım yapılandırmayı, CSP kurallarını oluşturmayı ve en iyi uygulamaları inceleyeceğiz.

Security Headers Neden Önemli?

Tarayıcılar, sunucudan gelen HTTP başlıklarına göre sayfanın davranışını kontrol eder. Doğru yapılandırılmış güvenlik başlıkları şunları sağlar:

  • XSS saldırılarının engellenmesi: Kötü niyetli script enjeksiyonlarını önler
  • Clickjacking koruması: Sayfanın iframe içinde gömülmesini kontrol eder
  • MIME sniffing engelleme: Tarayıcının içerik türünü tahmin etmesini önler
  • HTTPS zorunluluğu: Güvenli bağlantı kullanımını zorlar
  • Referrer bilgisi kontrolü: Üçüncü taraflara gönderilen bilgiyi sınırlar

next.config.js ile Temel Güvenlik Başlıkları

Next.js'te güvenlik başlıklarını next.config.js dosyasında headers fonksiyonu ile tanımlayabilirsiniz:

// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
  async headers() {
    return [
      {
        // Tüm route'lara uygula
        source: '/(.*)',
        headers: [
          {
            key: 'X-Frame-Options',
            value: 'DENY',
          },
          {
            key: 'X-Content-Type-Options',
            value: 'nosniff',
          },
          {
            key: 'Referrer-Policy',
            value: 'strict-origin-when-cross-origin',
          },
          {
            key: 'X-XSS-Protection',
            value: '1; mode=block',
          },
          {
            key: 'Permissions-Policy',
            value: 'camera=(), microphone=(), geolocation=()',
          },
          {
            key: 'Strict-Transport-Security',
            value: 'max-age=63072000; includeSubDomains; preload',
          },
        ],
      },
    ];
  },
};

module.exports = nextConfig;
Bilgi: X-Frame-Options: DENY başlığı, sayfanızın hiçbir iframe içinde gösterilmesine izin vermez. Belirli domainlere izin vermek istiyorsanız SAMEORIGIN kullanabilirsiniz.

Content Security Policy (CSP) Nedir?

CSP, tarayıcıya hangi kaynaklardan içerik yüklenmesine izin verildiğini bildiren güçlü bir güvenlik mekanizmasıdır. Script, stil, resim, font ve diğer kaynaklar için ayrı ayrı kurallar tanımlayabilirsiniz.

CSP Direktifleri

DirektifAçıklama
default-srcDiğer direktifler tanımlanmadığında varsayılan kaynak
script-srcJavaScript kaynaklarını kontrol eder
style-srcCSS kaynaklarını kontrol eder
img-srcGörsel kaynaklarını kontrol eder
font-srcFont kaynaklarını kontrol eder
connect-srcAPI isteklerini kontrol eder (fetch, XHR, WebSocket)
frame-ancestorsHangi domainlerin sayfayı iframe'de gösterebileceğini belirler

Next.js Middleware ile Nonce Tabanlı CSP

Nonce (Number used once) tabanlı CSP, her istekte benzersiz bir token oluşturarak yalnızca bu token'a sahip scriptlerin çalışmasına izin verir. Bu, inline scriptler için en güvenli yaklaşımdır:

// middleware.ts
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: https:;
    font-src 'self' https://fonts.gstatic.com;
    connect-src 'self' https://api.example.com;
    frame-ancestors 'none';
    form-action 'self';
    base-uri 'self';
    object-src 'none';
    upgrade-insecure-requests;
  `.replace(/\s{2,}/g, ' ').trim();

  const requestHeaders = new Headers(request.headers);
  requestHeaders.set('x-nonce', nonce);

  const response = NextResponse.next({
    request: { headers: requestHeaders },
  });

  response.headers.set('Content-Security-Policy', cspHeader);
  return response;
}

export const config = {
  matcher: [
    {
      source: '/((?!api|_next/static|_next/image|favicon.ico).*)',
      missing: [{ type: 'header', key: 'next-router-prefetch' }],
    },
  ],
};

Nonce'u Komponentlerde Kullanma

Middleware'den gelen nonce değerini Server Component'lerde okuyup script etiketlerine ekleyebilirsiniz:

// app/layout.tsx
import { headers } from 'next/headers';
import Script from 'next/script';

export default async function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  const headersList = await headers();
  const nonce = headersList.get('x-nonce') || '';

  return (
    <html lang="tr">
      <body>
        {children}
        <Script
          src="https://analytics.example.com/script.js"
          nonce={nonce}
          strategy="afterInteractive"
        />
      </body>
    </html>
  );
}

Permissions-Policy Yapılandırması

Permissions-Policy başlığı (eski adıyla Feature-Policy), tarayıcı özelliklerinin kullanımını kontrol eder:

{
  key: 'Permissions-Policy',
  value: [
    'camera=()',
    'microphone=()',
    'geolocation=()',
    'browsing-topics=()',
    'interest-cohort=()',
    'payment=(self)',
    'usb=()',
  ].join(', '),
}
Uyarı: CSP kurallarını çok katı ayarlamak, üçüncü parti scriptlerin (Google Analytics, reCAPTCHA vb.) çalışmasını engelleyebilir. Önce Content-Security-Policy-Report-Only başlığıyla test edin.

Report-Only Modu ile Test

CSP kurallarını uygulamadan önce Content-Security-Policy-Report-Only başlığıyla ihlalleri raporlayabilirsiniz:

// middleware.ts içinde
response.headers.set(
  'Content-Security-Policy-Report-Only',
  `${cspHeader}; report-uri /api/csp-report`
);

// app/api/csp-report/route.ts
import { NextRequest, NextResponse } from 'next/server';

export async function POST(request: NextRequest) {
  const body = await request.json();
  console.log('CSP İhlali:', JSON.stringify(body, null, 2));

  // İhlalleri loglama servisine gönder
  // await logService.report(body);

  return NextResponse.json({ status: 'ok' });
}

Vercel ve Farklı Ortam Yapılandırmaları

Ortama göre farklı CSP kuralları tanımlamak isteyebilirsiniz:

// lib/csp.ts
export function generateCSP(nonce: string) {
  const isDev = process.env.NODE_ENV === 'development';

  const directives: Record<string, string[]> = {
    'default-src': ["'self'"],
    'script-src': [
      "'self'",
      `'nonce-${nonce}'`,
      "'strict-dynamic'",
      ...(isDev ? ["'unsafe-eval'"] : []),
    ],
    'style-src': [
      "'self'",
      `'nonce-${nonce}'`,
      ...(isDev ? ["'unsafe-inline'"] : []),
    ],
    'img-src': ["'self'", 'blob:', 'data:', 'https:'],
    'connect-src': [
      "'self'",
      ...(isDev ? ['ws://localhost:3000'] : []),
      process.env.NEXT_PUBLIC_API_URL || '',
    ].filter(Boolean),
    'frame-ancestors': ["'none'"],
    'form-action': ["'self'"],
    'base-uri': ["'self'"],
    'object-src': ["'none'"],
  };

  if (!isDev) {
    directives['upgrade-insecure-requests'] = [];
  }

  return Object.entries(directives)
    .map(([key, values]) =>
      values.length ? `${key} ${values.join(' ')}` : key
    )
    .join('; ');
}

Güvenlik Başlıklarını Test Etme

Yapılandırmanızı kontrol etmek için şu araçları kullanabilirsiniz:

  1. securityheaders.com: Sitenizi tarayıp güvenlik başlıkları için puan verir
  2. Tarayıcı DevTools: Network sekmesinden response header'larını kontrol edin
  3. curl komutu: Komut satırından başlıkları inceleyin
# Güvenlik başlıklarını kontrol et
curl -I https://your-domain.com

# CSP başlığını detaylı incele
curl -s -D - https://your-domain.com -o /dev/null | grep -i content-security
İpucu: securityheaders.com üzerinden A+ notu almayı hedefleyin. Bu, tüm temel güvenlik başlıklarının doğru yapılandırıldığı anlamına gelir.

Sonuç

Next.js projelerinde güvenlik başlıkları ve CSP yapılandırması, uygulamanızı birçok yaygın web saldırısına karşı korur. next.config.js ile temel başlıkları, middleware ile nonce tabanlı dinamik CSP kurallarını uygulayabilirsiniz. Report-Only modu ile önce test edip, ardından production'da etkinleştirmeniz önerilir. Güvenlik sürekli bir süreçtir; başlıklarınızı düzenli olarak denetleyin ve güncel tehditlere göre güncelleyin.

Kaynaklar

İlgili Yazılar