Laravel Security Essentials: Melampaui Pengaturan Bawaan
Laravel hadir dengan konfigurasi keamanan default yang cukup solid. Namun untuk aplikasi production, dibutuhkan lapisan keamanan tambahan. Ini adalah rangkuman praktik terbaik yang saya gunakan saat mengamankan aplikasi Laravel di lingkungan dengan tuntutan tinggi.
Model Keamanan Berlapis (Security Onion)
Bayangkan keamanan seperti bawang—terdiri dari banyak lapisan yang melindungi inti aplikasimu:
- Lapisan Jaringan – Firewall, VPN, CDN
- Lapisan Aplikasi – Autentikasi, otorisasi, validasi
- Lapisan Database – Enkripsi, kontrol akses
- Lapisan Kode – Praktik coding yang aman
Melampaui Autentikasi Dasar
Multi-Factor Authentication (MFA)
// Menggunakan Laravel Fortify dengan MFA kustom
use Laravel\Fortify\Actions\DisableTwoFactorAuthentication;
use Laravel\Fortify\Actions\EnableTwoFactorAuthentication;
class CustomTwoFactorAuth
{
public function enable(Request $request)
{
$user = $request->user();
// Generate kode backup
$backupCodes = collect(range(1, 8))
->map(fn () => Str::random(10))
->toArray();
$user->forceFill([
'two_factor_recovery_codes' => encrypt(json_encode($backupCodes)),
])->save();
// Logika MFA kamu di sini
}
}
JWT dengan Refresh Token
class JWTAuthService
{
public function createTokenPair($user)
{
$accessToken = $this->createAccessToken($user);
$refreshToken = $this->createRefreshToken($user);
// Simpan refresh token hash di database
$user->refresh_tokens()->create([
'token_hash' => hash('sha256', $refreshToken),
'expires_at' => now()->addDays(30),
]);
return compact('accessToken', 'refreshToken');
}
}
Pola Keamanan di Database
Enkripsi Atribut
use Illuminate\Database\Eloquent\Casts\Attribute;
class User extends Model
{
protected function socialSecurityNumber(): Attribute
{
return Attribute::make(
get: fn ($value) => decrypt($value),
set: fn ($value) => encrypt($value),
);
}
}
Query Builder yang Aman
// ❌ Rentan terhadap SQL Injection
DB::select("SELECT * FROM users WHERE role = '{$role}'");
// ✅ Gunakan parameter
DB::select('SELECT * FROM users WHERE role = ?', [$role]);
// ✅ Lebih baik lagi: pakai Query Builder
User::where('role', $role)->get();
Pendalaman Keamanan API
Strategi Rate Limiting
// Rate limiter kustom di RouteServiceProvider
use Illuminate\Support\Facades\RateLimiter;
RateLimiter::for('api', function (Request $request) {
return Limit::perMinute(60)->by(
$request->user()?->id ?: $request->ip()
)->response(function () {
return response()->json([
'message' => 'Terlalu banyak permintaan. Coba lagi nanti.'
], 429);
});
});
Konfigurasi CORS
// config/cors.php
return [
'paths' => ['api/*'],
'allowed_methods' => ['GET', 'POST', 'PUT', 'DELETE'],
'allowed_origins' => [
'https://yourapp.com',
'https://admin.yourapp.com',
],
'allowed_origins_patterns' => [],
'allowed_headers' => ['*'],
'exposed_headers' => [],
'max_age' => 0,
'supports_credentials' => true,
];
Validasi & Sanitasi Input
Custom Validation Rule
class SecurePasswordRule implements Rule
{
public function passes($attribute, $value)
{
return preg_match('/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{12,}$/', $value);
}
public function message()
{
return 'Password minimal 12 karakter dan harus mengandung huruf besar, huruf kecil, angka, dan simbol.';
}
}
Keamanan Upload File
class FileUploadRequest extends FormRequest
{
public function rules(): array
{
return [
'file' => [
'required',
'file',
'max:2048', // 2MB
'mimes:jpg,jpeg,png,pdf',
new NoMaliciousContent,
],
];
}
}
class NoMaliciousContent implements Rule
{
public function passes($attribute, $value)
{
// Cek signatur file
$allowedSignatures = [
'jpg' => 'ffd8ffe0',
'png' => '89504e47',
'pdf' => '25504446',
];
$fileSignature = bin2hex(fread(fopen($value->path(), 'r'), 4));
return in_array($fileSignature, $allowedSignatures);
}
}
Middleware untuk Security Headers
class SecurityHeadersMiddleware
{
public function handle($request, Closure $next)
{
$response = $next($request);
$response->headers->set('X-Content-Type-Options', 'nosniff');
$response->headers->set('X-Frame-Options', 'DENY');
$response->headers->set('X-XSS-Protection', '1; mode=block');
$response->headers->set('Referrer-Policy', 'strict-origin-when-cross-origin');
$response->headers->set('Permissions-Policy', 'geolocation=(), microphone=(), camera=()');
// CSP Header
$csp = "default-src 'self'; script-src 'self' 'unsafe-inline' cdn.jsdelivr.net; style-src 'self' 'unsafe-inline' fonts.googleapis.com; font-src 'self' fonts.gstatic.com; img-src 'self' data: https:;";
$response->headers->set('Content-Security-Policy', $csp);
return $response;
}
}
Logging & Monitoring
Logging untuk Event Keamanan
class SecurityLogger
{
public static function logFailedLogin($email, $ip)
{
Log::channel('security')->warning('Percobaan login gagal', [
'email' => $email,
'ip' => $ip,
'user_agent' => request()->userAgent(),
'timestamp' => now(),
]);
}
public static function logSuspiciousActivity($user, $activity)
{
Log::channel('security')->alert('Aktivitas mencurigakan terdeteksi', [
'user_id' => $user->id,
'activity' => $activity,
'ip' => request()->ip(),
'timestamp' => now(),
]);
// Kirim notifikasi
event(new SuspiciousActivityDetected($user, $activity));
}
}
Inti yang Harus Diingat
- Defense in Depth – Gunakan keamanan berlapis
- Validasi Segalanya – Jangan pernah percaya input user
- Prinsip Least Privilege – Beri akses minimum yang diperlukan
- Monitoring & Logging – Tak bisa mengamankan hal yang tak terlihat
- Rutin Update – Selalu perbarui dependency
Tools yang Saya Rekomendasikan
- Laravel Security Checker – Cek dependency dari kerentanan
- OWASP ZAP – Untuk pentest aplikasi web
- Snyk – Scan kerentanan dependency
- Laravel Telescope – Monitoring aplikasi (khusus dev)
Keamanan bukan fitur tambahan di akhir—tapi mindset yang dibawa sejak awal. Pola-pola ini telah membantu saya membangun dan memelihara aplikasi Laravel yang aman di lingkungan production.
Kamu punya tips atau praktik keamanan favorit? Kasih tahu saya di LinkedIn.