LARAVEL SECURITY BACKEND PHP

Laravel Security Essentials: Melampaui Pengaturan Bawaan

⏱️ 7 menit baca
👨‍💻

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:

  1. Lapisan Jaringan – Firewall, VPN, CDN
  2. Lapisan Aplikasi – Autentikasi, otorisasi, validasi
  3. Lapisan Database – Enkripsi, kontrol akses
  4. 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

  1. Defense in Depth – Gunakan keamanan berlapis
  2. Validasi Segalanya – Jangan pernah percaya input user
  3. Prinsip Least Privilege – Beri akses minimum yang diperlukan
  4. Monitoring & Logging – Tak bisa mengamankan hal yang tak terlihat
  5. Rutin Update – Selalu perbarui dependency

Tools yang Saya Rekomendasikan

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.

🔗 Baca Juga