Advanced Laravel

Advanced Security Practices

20 min Lesson 34 of 40

Advanced Security Practices

Security is paramount in modern web applications. This lesson covers advanced security practices in Laravel, including protection against OWASP Top 10 vulnerabilities, implementing content security policies, and advanced encryption techniques.

OWASP Top 10 and Laravel

The OWASP Top 10 represents the most critical web application security risks. Laravel provides built-in protection for many of these, but understanding how they work is essential.

<?php

// 1. SQL Injection Prevention - Always use Eloquent or Query Builder
// BAD - Vulnerable to SQL injection
$users = DB::select("SELECT * FROM users WHERE email = '" . $request->email . "'");

// GOOD - Using parameter binding
$users = DB::select("SELECT * FROM users WHERE email = ?", [$request->email]);

// BEST - Using Eloquent
$users = User::where('email', $request->email)->get();

// 2. Cross-Site Scripting (XSS) Prevention
// Laravel automatically escapes output in Blade templates
// BAD - Unescaped output (use only for trusted content)
{!! $userInput !!}

// GOOD - Escaped output (default)
{{ $userInput }}

// Additional XSS protection for user input
use Illuminate\Support\Facades\Validator;

$validator = Validator::make($request->all(), [
    'comment' => ['required', 'string', 'max:1000', function ($attribute, $value, $fail) {
        if (preg_match('/(script|iframe|object|embed|applet)/i', $value)) {
            $fail('The comment contains potentially dangerous content.');
        }
    }],
]);

// Sanitizing HTML input with HTMLPurifier
composer require mews/purifier

use Mews\Purifier\Facades\Purifier;

$clean = Purifier::clean($request->input('content'));

// 3. Broken Authentication - Implementing secure authentication
// config/auth.php
return [
    'passwords' => [
        'users' => [
            'provider' => 'users',
            'table' => 'password_resets',
            'expire' => 60, // Token expiration
            'throttle' => 60, // Throttle attempts
        ],
    ],
];

// Implementing login throttling
use Illuminate\Support\Facades\RateLimiter;

public function login(Request $request)
{
    $key = 'login.' . $request->ip();

    if (RateLimiter::tooManyAttempts($key, 5)) {
        $seconds = RateLimiter::availableIn($key);
        return response()->json([
            'message' => "Too many login attempts. Try again in {$seconds} seconds."
        ], 429);
    }

    if (Auth::attempt($request->only('email', 'password'))) {
        RateLimiter::clear($key);
        $request->session()->regenerate();
        return redirect()->intended('dashboard');
    }

    RateLimiter::hit($key, 60);
    return back()->withErrors(['email' => 'Invalid credentials']);
}
Warning: Never store passwords in plain text or use reversible encryption. Always use Laravel's Hash facade which implements bcrypt or Argon2 hashing algorithms.

CSRF Protection Deep Dive

Cross-Site Request Forgery (CSRF) attacks trick users into performing unwanted actions. Laravel provides robust CSRF protection through tokens.

<?php

// CSRF middleware is automatically applied to web routes
// app/Http/Middleware/VerifyCsrfToken.php

protected $except = [
    'api/*',  // Exclude API routes
    'webhooks/*',  // Exclude webhook endpoints
];

// For AJAX requests, include CSRF token in headers
// resources/js/bootstrap.js
window.axios.defaults.headers.common['X-CSRF-TOKEN'] =
    document.querySelector('meta[name="csrf-token"]').getAttribute('content');

// In Blade templates
<meta name="csrf-token" content="{{ csrf_token() }}">

<form method="POST" action="/profile">
    @csrf
    <!-- form fields -->
</form>

// For method spoofing (PUT, PATCH, DELETE)
<form method="POST" action="/posts/1">
    @csrf
    @method('PUT')
    <!-- form fields -->
</form>

// Custom CSRF validation in controllers
use Illuminate\Support\Facades\Session;

public function verify(Request $request)
{
    $token = $request->input('_token');

    if (!hash_equals(Session::token(), $token)) {
        abort(419, 'CSRF token mismatch');
    }

    // Process request
}

// Rotating CSRF tokens after sensitive operations
public function updatePassword(Request $request)
{
    // Update password

    $request->session()->regenerateToken();

    return redirect()->back();
}

Content Security Policy (CSP)

Content Security Policy is a security standard that helps prevent XSS, clickjacking, and other code injection attacks by specifying which resources can be loaded.

<?php

// app/Http/Middleware/SetSecurityHeaders.php
namespace App\Http\Middleware;

use Closure;

class SetSecurityHeaders
{
    public function handle($request, Closure $next)
    {
        $response = $next($request);

        // Content Security Policy
        $response->headers->set('Content-Security-Policy',
            "default-src 'self'; " .
            "script-src 'self' 'nonce-" . csp_nonce() . "' https://cdn.jsdelivr.net; " .
            "style-src 'self' 'nonce-" . csp_nonce() . "' https://fonts.googleapis.com; " .
            "img-src 'self' data: https:; " .
            "font-src 'self' https://fonts.gstatic.com; " .
            "connect-src 'self'; " .
            "frame-ancestors 'none'; " .
            "base-uri 'self'; " .
            "form-action 'self';"
        );

        // X-Frame-Options (Clickjacking protection)
        $response->headers->set('X-Frame-Options', 'DENY');

        // X-Content-Type-Options (MIME sniffing protection)
        $response->headers->set('X-Content-Type-Options', 'nosniff');

        // X-XSS-Protection (Legacy XSS protection)
        $response->headers->set('X-XSS-Protection', '1; mode=block');

        // Referrer-Policy
        $response->headers->set('Referrer-Policy', 'strict-origin-when-cross-origin');

        // Permissions-Policy (Feature Policy)
        $response->headers->set('Permissions-Policy',
            'geolocation=(), microphone=(), camera=()'
        );

        // HTTP Strict Transport Security (HSTS)
        if ($request->secure()) {
            $response->headers->set('Strict-Transport-Security',
                'max-age=31536000; includeSubDomains; preload'
            );
        }

        return $response;
    }
}

// Helper function for CSP nonce
function csp_nonce()
{
    return app('csp-nonce');
}

// Register in AppServiceProvider
public function boot()
{
    $this->app->singleton('csp-nonce', function () {
        return base64_encode(random_bytes(16));
    });
}

// Use nonce in Blade templates
<script nonce="{{ csp_nonce() }}">
    // Your inline script
</script>
Tip: Use CSP in report-only mode initially to identify which resources your application loads without breaking functionality: Content-Security-Policy-Report-Only.

Advanced Encryption Practices

Laravel's encryption services provide a simple interface for encrypting and decrypting text using OpenSSL with AES-256 and AES-128 encryption.

<?php

use Illuminate\Support\Facades\Crypt;
use Illuminate\Contracts\Encryption\DecryptException;

// Basic encryption/decryption
$encrypted = Crypt::encryptString('Sensitive data');
$decrypted = Crypt::decryptString($encrypted);

// Encrypting objects and arrays
$encrypted = Crypt::encrypt([
    'credit_card' => '4111111111111111',
    'cvv' => '123',
]);

try {
    $decrypted = Crypt::decrypt($encrypted);
} catch (DecryptException $e) {
    // Handle decryption failure
    Log::error('Decryption failed: ' . $e->getMessage());
}

// Database encryption using casts
class User extends Model
{
    protected $casts = [
        'social_security_number' => 'encrypted',
        'bank_account' => 'encrypted:array',
        'credit_cards' => 'encrypted:collection',
    ];
}

// Accessing encrypted data
$user = User::find(1);
$ssn = $user->social_security_number; // Automatically decrypted

// Hashing passwords and sensitive data
use Illuminate\Support\Facades\Hash;

// Hash password
$hashed = Hash::make('secret-password');

// Verify password
if (Hash::check('secret-password', $hashed)) {
    // Password matches
}

// Check if rehashing is needed (algorithm updated)
if (Hash::needsRehash($hashed)) {
    $hashed = Hash::make('secret-password');
}

// Signing and verifying data integrity
use Illuminate\Support\Facades\URL;

// Create signed URL (expires in 30 minutes)
$url = URL::temporarySignedRoute(
    'download', now()->addMinutes(30), ['file' => 'report.pdf']
);

// Verify signed URL in controller
public function download(Request $request)
{
    if (!$request->hasValidSignature()) {
        abort(401, 'Invalid or expired download link');
    }

    return response()->download(storage_path('reports/' . $request->file));
}

// Two-factor authentication implementation
use PragmaRX\Google2FA\Google2FA;

$google2fa = new Google2FA();

// Generate secret key
$secretKey = $google2fa->generateSecretKey();

// Generate QR code URL
$qrCodeUrl = $google2fa->getQRCodeUrl(
    config('app.name'),
    auth()->user()->email,
    $secretKey
);

// Verify 2FA token
$valid = $google2fa->verifyKey($user->google2fa_secret, $request->input('token'));
Note: The encryption key in config/app.php (or APP_KEY in .env) is critical. If you lose it, all encrypted data becomes unrecoverable. Backup your encryption key securely and rotate it periodically.

Secure API Authentication

<?php

// Sanctum API token authentication with abilities
use Laravel\Sanctum\HasApiTokens;

class User extends Model
{
    use HasApiTokens;
}

// Creating tokens with specific abilities
$token = $user->createToken('mobile-app', [
    'posts:read',
    'posts:create',
    'comments:read',
])->plainTextToken;

// Protecting routes with abilities
Route::middleware(['auth:sanctum', 'ability:posts:create'])->group(function () {
    Route::post('/posts', [PostController::class, 'store']);
});

// Checking abilities in controllers
public function update(Request $request, Post $post)
{
    if (!$request->user()->tokenCan('posts:update')) {
        return response()->json(['error' => 'Unauthorized'], 403);
    }

    // Update post
}

// Implementing API key authentication
// app/Http/Middleware/CheckApiKey.php
namespace App\Http\Middleware;

use Closure;

class CheckApiKey
{
    public function handle($request, Closure $next)
    {
        $apiKey = $request->header('X-API-Key');

        if (!$apiKey || !$this->isValidApiKey($apiKey)) {
            return response()->json(['error' => 'Invalid API key'], 401);
        }

        return $next($request);
    }

    private function isValidApiKey($key)
    {
        return hash_equals(config('services.api.key'), $key);
    }
}

// Rate limiting API requests
// app/Providers/RouteServiceProvider.php
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Support\Facades\RateLimiter;

protected function configureRateLimiting()
{
    RateLimiter::for('api', function (Request $request) {
        return $request->user()
            ? Limit::perMinute(100)->by($request->user()->id)
            : Limit::perMinute(10)->by($request->ip());
    });

    RateLimiter::for('sensitive', function (Request $request) {
        return Limit::perMinute(5)
            ->by($request->user()->id)
            ->response(function () {
                return response()->json([
                    'error' => 'Too many attempts. Please try again later.'
                ], 429);
            });
    });
}

Exercise 1: Secure File Upload System

Create a secure file upload system with the following features:

  • Validate file types (whitelist approach) and size limits
  • Scan uploaded files for malware using ClamAV or similar
  • Store files outside the public directory
  • Generate signed URLs with expiration for downloads
  • Implement virus scanning queue job
  • Log all upload and download attempts with IP and user info

Exercise 2: Multi-Factor Authentication

Implement a complete 2FA system:

  • Allow users to enable/disable 2FA via settings
  • Generate and display QR codes for authenticator apps
  • Provide backup codes for account recovery
  • Implement 2FA challenge during login
  • Add "remember this device" functionality for 30 days
  • Send email notifications when 2FA is enabled/disabled

Exercise 3: Security Audit System

Build a comprehensive security audit system that:

  • Logs all authentication attempts (successful and failed)
  • Tracks IP addresses and user agents
  • Detects suspicious patterns (multiple failed logins, unusual locations)
  • Sends alerts for security events via email/Slack
  • Creates a dashboard showing recent security events
  • Implements automatic account lockout after threshold
  • Provides audit log export in CSV/JSON format