Laravel sudah membawa banyak perlindungan bawaan: token CSRF, password ter-hash, cookie terenkripsi, dan query yang memakai binding. Karena itu banyak developer merasa aplikasinya otomatis aman. Kenyataannya, sebagian besar insiden pada aplikasi Laravel justru terjadi karena konfigurasi produksi yang keliru atau fitur bawaan yang tidak dipakai dengan benar, misalnya file .env yang bisa diunduh siapa saja atau halaman edit invoice yang bisa dibuka pengguna lain hanya dengan mengganti angka di URL.
Artikel ini adalah checklist praktis untuk aplikasi Laravel 11 yang akan atau sudah berjalan di produksi. Setiap poin disertai cara mengeceknya, sehingga bisa langsung dipakai sebelum go-live atau saat mengaudit source code yang baru dibeli.
1. Konfigurasi Produksi: APP_DEBUG, APP_KEY, dan Lokasi .env
Tiga baris di .env ini paling sering menjadi sumber kebocoran:
APP_ENV=production
APP_DEBUG=false
APP_KEY=base64:... # dibuat dengan php artisan key:generate, jangan disalin dari proyek lain
Dengan APP_DEBUG=true, setiap error menampilkan stack trace, potongan kode, dan kadang isi variabel lingkungan seperti password database. APP_KEY dipakai untuk mengenkripsi cookie dan session; jika bocor atau dipakai bersama proyek lain, data terenkripsi bisa dibaca pihak lain.
Yang tidak kalah penting: document root web server harus mengarah ke folder public/, bukan ke root proyek. Jika salah, file .env, composer.json, bahkan folder storage/logs bisa diakses lewat browser. Cek dari komputer Anda:
curl -s -o /dev/null -w "%{http_code}\n" https://domainanda.com/.env
curl -s -o /dev/null -w "%{http_code}\n" https://domainanda.com/storage/logs/laravel.log
Keduanya harus menghasilkan 404 atau 403, bukan 200. Di shared hosting yang tidak bisa mengubah document root, ikuti langkah di panduan deploy Laravel ke shared hosting.
2. HTTPS dan Cookie yang Aman
Setelah sertifikat SSL terpasang, paksa semua URL memakai HTTPS dan pastikan cookie session hanya dikirim lewat koneksi terenkripsi:
# .env
APP_URL=https://domainanda.com
SESSION_SECURE_COOKIE=true
// app/Providers/AppServiceProvider.php
public function boot(): void
{
if ($this->app->environment('production')) {
\Illuminate\Support\Facades\URL::forceScheme('https');
}
}
3. Mass Assignment: Tentukan Kolom yang Boleh Diisi
Kode User::create($request->all()) terlihat praktis, tetapi jika model tidak membatasi kolom, penyerang bisa menambahkan field is_admin=1 atau saldo=99999999 ke form. Selalu definisikan $fillable dan isi model dari data yang sudah divalidasi:
class User extends Authenticatable
{
protected $fillable = ['name', 'email', 'password'];
}
// Controller
$data = $request->validate([
'name' => ['required', 'string', 'max:100'],
'email' => ['required', 'email', 'unique:users'],
'password' => ['required', 'min:8', 'confirmed'],
]);
User::create($data); // bukan $request->all()
4. Otorisasi: Cegah Pengguna Membuka Data Milik Orang Lain
Celah yang paling sering ditemukan pada aplikasi bisnis adalah IDOR (Insecure Direct Object Reference): pengguna yang sudah login bisa membuka /invoice/15/edit milik pelanggan lain hanya dengan mengganti angka. Middleware auth tidak cukup, karena ia hanya memastikan pengguna sudah login, bukan bahwa data itu miliknya. Gunakan Policy:
php artisan make:policy InvoicePolicy --model=Invoice
// app/Policies/InvoicePolicy.php
public function update(User $user, Invoice $invoice): bool
{
return $invoice->user_id === $user->id;
}
// Controller (Laravel 11)
use Illuminate\Support\Facades\Gate;
public function edit(Invoice $invoice)
{
Gate::authorize('update', $invoice); // otomatis 403 bila bukan pemiliknya
return view('invoice.edit', compact('invoice'));
}
Untuk aplikasi dengan banyak peran (admin, kasir, gudang), kombinasikan Policy dengan paket peran seperti dijelaskan di artikel role dan permission dengan Spatie.
5. Batasi Percobaan Login (Rate Limiting)
Tanpa pembatasan, form login bisa ditebak ribuan kali per menit. Definisikan limiter di AppServiceProvider, lalu pasang di route login:
// app/Providers/AppServiceProvider.php
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\RateLimiter;
public function boot(): void
{
RateLimiter::for('login', function (Request $request) {
return Limit::perMinute(5)->by(strtolower($request->input('email')) . '|' . $request->ip());
});
}
// routes/web.php
Route::post('/login', [LoginController::class, 'store'])->middleware('throttle:login');
Setelah lima percobaan gagal dalam satu menit dari kombinasi email dan IP yang sama, Laravel membalas HTTP 429. Starter kit resmi seperti Breeze sudah menerapkan pola serupa; jika Anda membuat form login sendiri, langkah ini wajib ditambahkan manual.
6. Cegah XSS di Blade
Sintaks {{ $variabel }} di Blade otomatis meng-escape HTML, sehingga skrip yang disisipkan pengguna tampil sebagai teks biasa. Bahaya muncul ketika memakai {!! $variabel !!} untuk data yang berasal dari pengguna, misalnya catatan pelanggan atau nama produk. Gunakan {!! !!} hanya untuk HTML yang Anda tulis sendiri atau yang sudah dibersihkan dengan library sanitizer. Cari semua pemakaiannya dengan:
grep -rn "{!!" resources/views/
7. Upload File: Validasi Tipe dan Simpan di Luar Folder Publik
Fitur unggah bukti transfer atau foto produk adalah jalur favorit untuk menanam file PHP berbahaya. Terapkan tiga aturan: batasi tipe dan ukuran, biarkan Laravel membuat nama file acak, dan simpan file sensitif di disk privat.
$request->validate([
'bukti' => ['required', 'file', 'mimes:jpg,jpeg,png,pdf', 'max:2048'], // 2 MB
]);
// Disk "local" Laravel 11 berada di storage/app/private, tidak bisa diakses langsung lewat URL
$path = $request->file('bukti')->store('bukti-transfer');
// Menampilkan file lewat controller yang memeriksa hak akses
public function bukti(Order $order)
{
Gate::authorize('view', $order);
return Storage::download($order->bukti_path);
}
Jangan pernah memakai nama file asli dari pengguna sebagai nama simpanan, dan jangan menyimpan unggahan pengguna di public/ tanpa validasi tipe.
8. Tambahkan Security Header
Beberapa header HTTP sederhana menutup serangan seperti clickjacking dan MIME sniffing. Buat middleware lalu daftarkan secara global:
// app/Http/Middleware/SecurityHeaders.php
public function handle(Request $request, Closure $next): Response
{
$response = $next($request);
$response->headers->set('X-Frame-Options', 'SAMEORIGIN');
$response->headers->set('X-Content-Type-Options', 'nosniff');
$response->headers->set('Referrer-Policy', 'strict-origin-when-cross-origin');
return $response;
}
// bootstrap/app.php
->withMiddleware(function (Middleware $middleware) {
$middleware->append(\App\Http\Middleware\SecurityHeaders::class);
})
9. Perbarui Dependency Secara Berkala
Celah keamanan juga datang dari paket pihak ketiga. Composer 2.4 ke atas punya perintah bawaan untuk mengecek paket yang punya laporan kerentanan:
composer audit
Jalankan sebelum setiap deploy. Jika ada temuan, perbarui paket terkait dengan composer update nama/paket, lalu jalankan test sebelum naik ke produksi.
10. Siapkan Log dan Backup
Keamanan juga berarti bisa pulih ketika terjadi masalah. Pastikan storage/logs tidak membengkak tanpa batas (pakai channel daily dengan LOG_DAILY_DAYS), catat kejadian penting seperti login gagal berulang, dan jadwalkan backup database otomatis yang disimpan di luar server aplikasi.
Tabel Checklist Sebelum Go-Live
| Area | Yang dicek | Cara cepat mengecek |
|---|---|---|
| Konfigurasi | APP_DEBUG=false, APP_KEY unik | Buka URL yang tidak ada; tidak boleh muncul stack trace |
| Document root | Mengarah ke public/ | curl ke /.env harus 404/403 |
| HTTPS | Redirect ke https, cookie secure | Buka versi http://, harus berpindah ke https:// |
| Mass assignment | Semua model punya $fillable | grep -rn "request()->all()\|\$request->all()" app/ |
| Otorisasi | Data milik pengguna dilindungi Policy | Login sebagai user A, buka ID milik user B: harus 403 |
| Login | Rate limit aktif | Coba login salah 6 kali: harus 429 |
| XSS | {!! !!} tidak dipakai untuk input pengguna | grep -rn "{!!" resources/views/ |
| Upload | Validasi mimes dan max, disimpan privat | Coba unggah file .php: harus ditolak |
| Dependency | Tidak ada paket rentan | composer audit |
Kesalahan yang Sering Terjadi
- Mengandalkan menu yang disembunyikan. Menyembunyikan tombol "Hapus" untuk kasir tidak mencegah kasir memanggil URL hapus secara langsung. Pengecekan hak akses harus ada di server.
- Menyalin
.envdari lokal ke produksi. AkibatnyaAPP_DEBUGmasihtruedanAPP_KEYsama dengan mesin developer. - Membiarkan route debug atau tool development aktif. Pastikan paket seperti debugbar hanya dipasang sebagai
require-devdan produksi diinstal dengancomposer install --no-dev.
Sebagian besar poin di atas hanya butuh beberapa menit, tetapi menutup celah yang paling sering dieksploitasi. Lanjutkan dengan membaca panduan mencegah SQL injection untuk memastikan lapisan database juga aman, dan cara kerja middleware Laravel jika ingin memahami di mana pengecekan keamanan dijalankan.