· Admin

Laravel'de CORS Yapılandırması

API geliştiriyorsunuz, frontend React veya Vue ile ayrı bir domain'de çalışıyor, AJAX isteği atıyorsunuz ve browser konsolunda bu hatayı görüyorsunuz:

Access to XMLHttpRequest at 'https://api.projeman.net/api/users'
from origin 'https://projeman.net' has been blocked by CORS policy

CORS — Cross-Origin Resource Sharing. Browser'ın güvenlik mekanizması. Bir domain'den başka bir domain'e JavaScript ile istek atmayı engelliyor (aynı origin dışına). Amacı kötü niyetli sitelerin API'nize istek atmasını önlemek.

Laravel'in Built-in CORS Desteği

Laravel 7'den itibaren CORS desteği framework'e dahil. config/cors.php dosyasından ayarlanır:

return [
    'paths' => ['api/*', 'sanctum/csrf-cookie'],

    'allowed_methods' => ['*'],

    'allowed_origins' => ['*'],

    'allowed_origins_patterns' => [],

    'allowed_headers' => ['*'],

    'exposed_headers' => [],

    'max_age' => 0,

    'supports_credentials' => false,
];

Default ayarlar her şeye izin verir (*). Development için sorun yok ama production'da sıkılaştırmanız gerekir.

Production Ayarları

return [
    // Hangi path'lere CORS uygulansın
    'paths' => ['api/*'],

    // İzin verilen HTTP metodları
    'allowed_methods' => ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],

    // İzin verilen origin'ler — sadece kendi frontend'leriniz
    'allowed_origins' => [
        'https://projeman.net',
        'https://www.projeman.net',
        'https://admin.projeman.net',
    ],

    // Pattern ile origin eşleştirme (subdomain wildcard)
    'allowed_origins_patterns' => [
        '#^https://.*\.projeman\.net$#',
    ],

    // İzin verilen request header'ları
    'allowed_headers' => [
        'Content-Type',
        'Authorization',
        'X-Requested-With',
        'Accept',
    ],

    // Response'da frontend'in okuyabileceği header'lar
    'exposed_headers' => [
        'X-Total-Count',
        'X-Page-Count',
    ],

    // Preflight cache süresi (saniye) — 1 saat
    'max_age' => 3600,

    // Cookie/session gönderilecekse true
    'supports_credentials' => true,
];

Her ayarı açıklayayım:

paths — CORS kurallarının uygulanacağı URL pattern'leri. api/* demek sadece API route'larına uygulanır. Web route'larına CORS gerekmez çünkü onlar zaten aynı domain'den serve edilir.

allowed_origins — Hangi domain'lerden istek kabul ediyorsunuz. * yerine explicit domain listesi vermek en güvenli yaklaşım. Kendi frontend domain'lerinizi yazın.

allowed_origins_patterns — Regex ile origin eşleştirme. *.projeman.net gibi wildcard subdomain'ler için kullanışlı.

max_age — Browser preflight (OPTIONS) isteğinin sonucunu ne kadar süre cache'lesin. 3600 saniye = 1 saat. Her istek öncesi preflight atmasını önler, performansı artırır.

supports_credentials — Frontend withCredentials: true ile cookie gönderiyorsa bu true olmalı. Ama allowed_origins artık * olamaz — explicit domain listesi zorunlu olur.

Preflight Nedir?

Browser, bazı isteklerde (POST, PUT, DELETE veya özel header'lı GET) asıl isteği atmadan önce bir OPTIONS isteği gönderir. Buna "preflight" denir. Sunucu "evet, bu origin'e izin veriyorum" derse browser asıl isteği atar.

Browser → OPTIONS /api/users (preflight)
Server → 200 OK + CORS header'ları
Browser → POST /api/users (asıl istek)
Server → 201 Created

max_age bu preflight sonucunu cache'ler — aynı endpoint'e tekrar istek atarken preflight atlanır.

Environment Bazlı Ayarlar

Development ve production'da farklı origin'ler istiyorsanız .env kullanın:

// config/cors.php
'allowed_origins' => explode(',', env('CORS_ALLOWED_ORIGINS', '*')),
# .env.local (development)
CORS_ALLOWED_ORIGINS=http://localhost:3000,http://localhost:5173

# .env.production
CORS_ALLOWED_ORIGINS=https://projeman.net,https://www.projeman.net

Bu sayede development'ta Vite dev server'a (5173), React dev server'a (3000) izin verirsiniz. Production'da sadece kendi domain'lerinize.

Middleware Sırası

Laravel'in HandleCors middleware'i bootstrap/app.php'de (Laravel 11+) veya Kernel.php'de (eski sürümler) global middleware olarak kayıtlıdır. Genelde dokunmanıza gerek kalmaz.

Eğer middleware sırasında sorun yaşıyorsanız — CORS header'ları response'a eklenmiyor gibi — HandleCors'un diğer middleware'lerden önce çalıştığından emin olun:

// bootstrap/app.php (Laravel 11+)
->withMiddleware(function (Middleware $middleware) {
    $middleware->prepend(\Illuminate\Http\Middleware\HandleCors::class);
})

Yaygın Sorunlar ve Çözümleri

"No 'Access-Control-Allow-Origin' header"

En sık karşılaşılan hata. Nedenleri:

  1. config/cors.php'de origin izin listesinde değil → Origin'i ekleyin
  2. Path pattern eşleşmiyor → paths array'ine route pattern'i ekleyin
  3. Nginx/Apache CORS header'ları ezmiş → Web server config'i kontrol edin
  4. Route bulunamıyor (404) → Route tanımını kontrol edin, 404 response'da CORS header'ı olmaz

Nginx ile Çakışma

Nginx'te de CORS header'ları ekliyorsanız Laravel ile çakışır — header iki kez gönderilir. Ya Nginx'ten kaldırın ya Laravel'den:

# Nginx'te CORS header'larını KALDIRIN
# add_header 'Access-Control-Allow-Origin' '*';  ← Bunu silın

Laravel'in HandleCors middleware'i yeterli. Nginx'e ayrıca eklemek gereksiz ve sorun yaratır.

API Token ile CORS

Sanctum veya Passport kullanıyorsanız:

'paths' => ['api/*', 'sanctum/csrf-cookie'],
'supports_credentials' => true,

Frontend'de:

// Axios
axios.defaults.withCredentials = true;

// Fetch
fetch('https://api.projeman.net/api/users', {
    credentials: 'include',
    headers: {
        'Authorization': 'Bearer ' + token,
    },
});

supports_credentials: true ise allowed_origins: ['*'] kullanamazsınız. Browser bunu reddeder. Explicit domain listesi şart.

Test Etmek

CORS ayarlarınızı curl ile test edebilirsiniz:

# Preflight isteği simüle et
curl -X OPTIONS https://api.projeman.net/api/users \
  -H "Origin: https://projeman.net" \
  -H "Access-Control-Request-Method: POST" \
  -H "Access-Control-Request-Headers: Content-Type, Authorization" \
  -v

# Response header'larında şunları görmelisiniz:
# Access-Control-Allow-Origin: https://projeman.net
# Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE, OPTIONS
# Access-Control-Allow-Headers: Content-Type, Authorization
# Access-Control-Max-Age: 3600

CORS ayarları genelde "bir kez ayarla unut" türünde bir iş. Ama yanlış ayarlandığında saatlerce debug ettirebilir. Production'a çıkmadan config/cors.php'yi gözden geçirin, * yerine explicit değerler koyun, test edin.