Sebagian besar insiden keamanan tidak bermula dari kode yang di-hack, melainkan dari satu baris .env yang salah: APP_DEBUG=true yang lupa dimatikan. Konfigurasi adalah bagian keamanan yang paling murah diperbaiki — tidak perlu menulis logika baru, cukup disiplin memasang saklar yang benar di environment yang benar. Chapter ini memeriksa lima titik konfigurasi produksi: mode debug, cookie flag, HTTPS, trusted hosts, dan header keamanan. Masing-masing kita uji dengan request sungguhan agar terlihat apa yang berubah sebelum dan sesudahnya.
APP_DEBUG dan Kebocoran Informasi #
Kita siapkan route yang sengaja error dengan pesan berisi kredensial fiktif:
Route::get('/bayar', function () {
throw new RuntimeException('Koneksi ke gateway pembayaran gagal: sk-RAHASIA-PRODUKSI');
});Di environment lokal, APP_DEBUG=true membuat halaman error menampilkan pesan exception dan stack trace. Kita tangkap responsnya saat error terjadi, dengan kedua kondisi .env:
=== APP_DEBUG=true ===
HTTP status: 500
panjang body: 135638 karakter
kutipan sekitar pesan: ... RuntimeException - Internal Server Error
Koneksi ke gateway pembayaran gagal: sk-RAHASIA-PRODUKSI
PHP 8.4.8 / Laravel 13.36.0
## Stack Trace
0 - routes/web.php:10
1 - vendor/laravel/framework/src/Illuminate/Routing\...
pesan exception bocor ke pengguna: YA
=== APP_DEBUG=false ===
HTTP status: 500
panjang body: 5648 karakter
kutipan body: Server Error ...
pesan exception bocor ke pengguna: TIDAKPerbedaannya drastis: 135.638 karakter berisi pesan exception, versi PHP, path file, dan seluruh environment request — versus 5.648 karakter halaman generik “Server Error” tanpa satu pun detail. Halaman debug yang detail adalah survei gratis bagi penyerang untuk memetakan struktur aplikasi. Aturannya tegas: APP_DEBUG=false di produksi, dan detail error tetap tersedia lewat log aplikasi
— bukan lewat browser pengguna.
Cookie Flag: http_only, same_site, secure #
Cookie session adalah benda paling bernilai di browser pengguna, dan Laravel memberinya tiga atribut pelindung. Kita cek cookie yang benar-benar dibuat oleh aplikasi:
=== cookie yang dibuat (attribut) ===
XSRF-TOKEN | HttpOnly: tidak | Secure: tidak | SameSite: lax
laravel-session | HttpOnly: YA | Secure: tidak | SameSite: lax
=== konfigurasi session terkait ===
session.http_only : true
session.same_site : lax
session.secure : NULL
session.encrypt : falseCookie laravel-session beratribut HttpOnly — JavaScript tidak bisa membacanya, sehingga script XSS yang menyusup tidak bisa mencuri session. Sebaliknya XSRF-TOKEN sengaja tanpa HttpOnly karena request AJAX perlu membacanya untuk header X-XSRF-TOKEN (mekanisme ini dibahas di CSRF Protection
). Arti tiap flag:
| Flag | Nilai aman produksi | Melindungi dari |
|---|---|---|
http_only | true (bawaan) | Pencurian cookie oleh script XSS |
same_site=lax | bawaan | Permintaan lintas situs (CSRF ringan) |
same_site=strict | opsional | Semua konteks lintas situs, termasuk tautan masuk |
secure | true di HTTPS | Pengiriman cookie lewat koneksi tak terenkripsi |
encrypt | opsional | Pembacaan isi session di sisi klien |
Nilai Secure: tidak di atas wajar untuk environment lokal HTTP. Di produksi HTTPS, aktifkan dengan SESSION_SECURE_COOKIE=true di .env — cookie session hanya dikirim lewat HTTPS. Opsi SESSION_ENCRYPT=true menambahkan enkripsi isi session jika data session Anda sensitif.
HTTPS dan Trusted Proxies #
Dua langkah menjadikan HTTPS sebagai jalur resmi aplikasi. Pertama, di balik reverse proxy (nginx, load balancer), Laravel perlu diberi tahu agar memercayai header X-Forwarded-* dari proxy tersebut — tanpa ini, request()->isSecure() selalu false walau koneksi user sebenarnya HTTPS:
// bootstrap/app.php
->withMiddleware(function (Middleware $middleware): void {
$middleware->trustProxies(at: [
'10.0.0.0/8', // rentang IP internal load balancer Anda
]);
})Kedua, paksa skema HTTPS untuk semua URL yang dihasilkan aplikasi (aset, redirect, tautan email):
// app/Providers/AppServiceProvider.php
use Illuminate\Support\Facades\URL;
public function boot(): void
{
if ($this->app->environment('production')) {
URL::forceScheme('https');
}
}Trusted Hosts #
Header Host bisa dipalsukan penyerang. Tanpa pembatasan, tautan verifikasi email atau reset password bisa dibuat mengarah ke domain palsu (aplikasi-evil.test) yang menampung halaman login tiruan. Laravel 11 ke atas menyaringnya dari bootstrap/app.php:
->withMiddleware(function (Middleware $middleware): void {
$middleware->trustHosts(at: ['aplikasi.example.com']);
})Dengan daftar itu, request dengan Host: di luar domain yang terdaftar ditolak. Verifikasi domain semacam ini termasuk rekomendasi standar proses deployment
resmi Laravel.
Header Keamanan Dasar #
Respons HTTP membawa header yang menginstruksikan browser untuk menolak perilaku berbahaya. Laravel tidak memasang semuanya secara default, jadi kita tambahkan middleware sendiri:
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class TambahHeaderKeamanan
{
public function handle(Request $request, Closure $next): Response
{
$response = $next($request);
$response->headers->set('X-Frame-Options', 'DENY');
$response->headers->set('X-Content-Type-Options', 'nosniff');
$response->headers->set('Referrer-Policy', 'strict-origin-when-cross-origin');
$response->headers->set('Permissions-Policy', 'camera=(), microphone=(), geolocation=()');
return $response;
}
}Middleware ini didaftarkan lewat $middleware->append(TambahHeaderKeamanan::class) di bootstrap/app.php. Kita uji respons setelah terpasang:
=== header keamanan pada respons ===
X-Frame-Options : DENY
X-Content-Type-Options : nosniff
Referrer-Policy : strict-origin-when-cross-origin
Permissions-Policy : camera=(), microphone=(), geolocation=()Empat header ini menutup vektor klasik: X-Frame-Options: DENY mencegah halaman Anda di-framing di situs lain (clickjacking), nosniff mencegah browser menafsirkan file sebagai tipe lain, Referrer-Policy membatasi bocornya URL internal, dan Permissions-Policy mematikan akses kamera serta lokasi. Untuk perlindungan XSS yang lebih dalam, tambahkan Content-Security-Policy — kuncinya membatasi sumber script (script-src 'self') agar script dari domain asing tidak dieksekusi, melengkapi escape Blade
yang sudah berjalan.
Konfigurasi yang aman bukan pekerjaan sekali jalan — ia melapuk seiring waktu karena dependency baru dan fitur baru. Kebiasaan yang menutup chapter section ini adalah ritual berkala: Security Auditing .