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;
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
| Direktif | Açıklama |
|---|---|
default-src | Diğer direktifler tanımlanmadığında varsayılan kaynak |
script-src | JavaScript kaynaklarını kontrol eder |
style-src | CSS kaynaklarını kontrol eder |
img-src | Görsel kaynaklarını kontrol eder |
font-src | Font kaynaklarını kontrol eder |
connect-src | API isteklerini kontrol eder (fetch, XHR, WebSocket) |
frame-ancestors | Hangi 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(', '),
}
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:
- securityheaders.com: Sitenizi tarayıp güvenlik başlıkları için puan verir
- Tarayıcı DevTools: Network sekmesinden response header'larını kontrol edin
- 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
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.