Secure Laravel: OWASP Top 10 in Practice

Khimananda Oli 8 min read DevOps
Secure Laravel: OWASP Top 10 in Practice

By Khimananda Oli | Last reviewed: August 2026

Shipping a Laravel application without addressing the OWASP Top 10 exposes your users and business to preventable breaches, from injection attacks to broken access control. Secure Laravel: OWASP Top 10 in Practice means moving beyond theoretical checklists to implement framework-native defenses that actually hold up under audit and attack. This guide translates each vulnerability category into concrete Laravel configurations, middleware patterns, and infrastructure controls you can deploy today.

Client RequestWAF / CloudflareRate Limit + BlockSQLi SignaturesLaravel MiddlewareCSRF Token CheckAuth + Policy GateHeader InjectionInput ValidationEloquent ORMParameterized Queries
Defense-in-depth architecture for Secure Laravel: OWASP Top 10 in Practice spanning edge WAF, application middleware, and database abstraction

How do you prevent injection and broken access control in Laravel?

Injection flaws (A03:2021) and Broken Access Control (A01:2021) remain the most critical risks because they directly enable data exfiltration or privilege escalation. In Laravel, Eloquent’s query builder uses PDO prepared statements by default, which neutralizes SQL injection when used correctly. The danger arises when developers bypass this safety with raw expressions or string concatenation.

Eliminating SQL injection vectors

Never interpolate user input into raw queries. Even seemingly safe contexts like orderBy or column names can be exploited if not validated against an allowlist. Use Eloquent’s built-in methods whenever possible:

<?php
// SAFE: Parameterized binding via Eloquent
$users = User::where('email', $request->input('email'))
    ->where('status', 'active')
    ->get();

// UNSAFE: Raw expression with unvalidated input
// DB::select("SELECT * FROM users WHERE email = '" . $request->input('email') . "'");

// SAFE: Validated column allowlist for dynamic sorting
$allowedSorts = ['name', 'created_at', 'email'];
$sortColumn = in_array($request->input('sort'), $allowedSorts, true)
    ? $request->input('sort')
    : 'created_at';

$users = User::orderBy($sortColumn, 'asc')->paginate(25);

Enforcing authorization with policies and gates

Broken access control often stems from missing ownership checks rather than authentication failures. Laravel Policies bind authorization logic directly to models, making it impossible to forget a permission check in controllers. Register policies in AuthServiceProvider and always call $this->authorize() or the @can Blade directive:

<?php
// app/Policies/PostPolicy.php
public function update(User $user, Post $post): bool
{
    return $user->id === $post->user_id || $user->hasRole('editor');
}

// In controller — fails with 403 if unauthorized
public function update(Request $request, Post $post)
{
    $this->authorize('update', $post);
    // ... validated update logic
}

This pattern ensures every mutation passes through centralized authorization. For API resources using Sanctum, pair policies with token-scoped abilities to enforce least-privilege access at both the token and model level.

What are the essential cryptographic and session security configurations?

Cryptographic Failures (A02:2021) and Insecure Design (A04:2021) frequently manifest as weak password hashing, exposed secrets, or misconfigured sessions. Laravel ships with secure defaults, but production deployments require explicit verification and hardening.

Password hashing and secret management

Laravel 11+ uses Argon2id as the default hashing algorithm, which resists GPU-based cracking better than bcrypt. Verify your config/hashing.php specifies 'driver' => 'argon2id'. Never store API keys, database passwords, or third-party tokens in code or .env files committed to version control. Use environment variables injected at deploy time, or integrate with a dedicated secrets manager like HashiCorp Vault for rotation and audit trails as outlined in secrets management best practices.

Sessions are a prime target for hijacking. Configure these values explicitly in config/session.php and config/services.php:

  • SameSite: Set to 'lax' or 'strict' to prevent CSRF via cross-site requests
  • Secure: Always true in production (requires HTTPS)
  • HttpOnly: Always true to block JavaScript access to session cookies
  • Lifetime: Reduce to 120 minutes or less for sensitive applications
  • Encrypt: Enable 'encrypt' => true to protect session payload confidentiality

For applications handling financial or health data, consider rotating session IDs after authentication and privilege changes to prevent fixation attacks. Laravel’s $request->session()->regenerate() handles this automatically during login when using the built-in auth scaffolding.

HTTP RequestVerifyCsrfTokenReject invalid tokensExclude APIs safelyAuthenticateSession / SanctumRegenerate IDAuthorizePolicy / Gate check403 on failureRoute
Middleware execution order for Secure Laravel: OWASP Top 10 in Practice ensuring CSRF, auth, and authorization run before business logic

How do you configure HTTP security headers and frontend protections?

Security Misconfiguration (A05:2021) and Vulnerable Components (A06:2021) account for many breaches that originate outside application code. HTTP security headers instruct browsers to enforce isolation, prevent MIME sniffing, and restrict resource loading. Laravel does not set these by default, so you must add them via middleware.

Implementing a security headers middleware

Create app/Http/Middleware/SecurityHeaders.php and register it globally in bootstrap/app.php:

<?php
namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;

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

        $response->headers->set('X-Content-Type-Options', 'nosniff');
        $response->headers->set('X-Frame-Options', 'SAMEORIGIN');
        $response->headers->set('Referrer-Policy', 'strict-origin-when-cross-origin');
        $response->headers->set('Permissions-Policy', 'camera=(), microphone=(), geolocation=()');
        $response->headers->set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload');
        
        // CSP tailored to your app — start restrictive, relax as needed
        $response->headers->set('Content-Security-Policy', 
            "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; frame-ancestors 'self'"
        );

        return $response;
    }
}

Test your headers with Mozilla Observatory or Security Headers scanner after deployment. A common mistake is setting an overly permissive CSP that includes unsafe-eval or wildcard sources, which defeats XSS protection entirely.

Keeping dependencies patched

Run composer audit weekly and integrate it into your CI pipeline as shown in GitLab CI for Laravel. Subscribe to GitHub Security Advisories for Laravel and key packages. Pin major versions in composer.json and test upgrades in staging before production. Outdated packages with known CVEs are low-hanging fruit for attackers and auditors alike.

What logging and monitoring practices satisfy compliance requirements?

Logging Failures (A09:2021) and SSRF (A10:2021) round out the Top 10 because they enable undetected exploitation and internal network pivoting. Compliance frameworks like SOC 2 and ISO 27001 require demonstrable audit trails, not just log files sitting on disk.

Structured logging for security events

Configure Laravel to write structured JSON logs to stdout/stderr so container orchestrators and log aggregators can parse them reliably. In config/logging.php, use the stderr channel with the json formatter. Log authentication attempts, authorization failures, password resets, and sensitive data access with consistent fields (event, user_id, ip, outcome). Never log passwords, tokens, or PII.

Monitoring and alerting on anomalies

Ship logs to a centralized stack like ELK or Grafana Loki as detailed in centralized logging setup. Create alerts for: brute-force login attempts (>5 failures/min), unexpected admin actions, large data exports, and outbound requests to unknown hosts (SSRF indicators). Pair this with Prometheus metrics tracking error rates and latency spikes that may signal active exploitation.

OWASP CategoryLaravel Native DefenseInfrastructure ComplementAudit Evidence
A01: Broken Access ControlPolicies, Gates, MiddlewareWAF rule sets, IAM rolesPolicy test coverage, access logs
A02: Cryptographic FailuresArgon2id, EncrypterTLS 1.3, KMS/VaultHash config, cert expiry alerts
A03: InjectionEloquent ORM, ValidationDB firewall, parameterized RDSQuery logs, SAST reports
A05: Security MisconfigurationHeaders middleware, env separationHardened AMIs, VPC segmentationConfig scans, CIS benchmarks
A09: Logging FailuresStructured JSON loggingCentralized SIEM, retention policiesLog integrity checks, alert tests
Insecure Pattern• Raw DB::select with string concat• No policy checks in controllers• Missing security headers• Logs only to local file, no alertsSecure Pattern• Eloquent parameterized queries• Policy enforcement + tests• CSP + HSTS + X-Frame-Options• Structured logs → SIEM + alertsVSOutcome DifferenceInsecure: Data breach in hours, failed audit, customer churnSecure: Attack blocked at edge, audit passed, trust retained
Before-and-after comparison demonstrating why Secure Laravel: OWASP Top 10 in Practice prevents breaches and satisfies compliance audits

Secure Laravel: OWASP Top 10 in Practice as Continuous Discipline

Security is not a feature you ship once; it is a continuous discipline embedded in your development workflow, infrastructure provisioning, and incident response. The controls outlined here — parameterized queries, policy-based authorization, hardened headers, structured logging, and dependency auditing — form the baseline for any Laravel application handling real user data. Revisit them quarterly, test them with automated tools and manual penetration testing, and treat every new feature as an opportunity to verify your defenses still hold. If your team needs help implementing these controls or preparing for a compliance audit, reach out to discuss your specific architecture.

Frequently Asked Questions

Eloquent ORM and Query Builder use PDO parameter binding, which separates SQL logic from data. This prevents malicious input from altering query structure. Avoid raw expressions unless absolutely necessary, and always validate inputs when using DB::raw or whereRaw methods in your application code.

Blade automatically escapes output using double curly braces. Use this syntax for all user-generated content. Only use unescaped syntax with extreme caution and after applying HTMLPurifier or similar sanitization libraries to strip dangerous tags and attributes from rich text inputs.

Include the @csrf directive in every HTML form. The VerifyCsrfToken middleware validates tokens automatically. For API routes, use Sanctum token authentication instead of session-based CSRF tokens to secure stateless endpoints against cross-site request forgery attacks effectively.

No. You must implement authorization manually using Gates or Policies. Define permissions for each model action and check them in controllers and views. Never rely solely on authentication; always verify the current user has explicit permission to access specific resources or perform sensitive operations.

Use roave/security-advisories as a dev dependency. It blocks installation of packages with known CVEs via Composer. Run composer audit regularly in CI pipelines to catch newly disclosed vulnerabilities in third-party libraries before they reach production environments.

Keep secrets in .env files locally and use cloud secret managers like AWS Secrets Manager or HashiCorp Vault in production. Never commit .env to version control. Rotate credentials periodically and restrict access using IAM roles rather than static keys.

Partially. Laravel sets some defaults, but you should add spatie/laravel-csp or configure middleware manually for strict CSP, HSTS, and X-Frame-Options. Test headers with securityheaders.com to ensure proper configuration against clickjacking, MIME sniffing, and other browser-based attack vectors.

Always define $fillable or $guarded properties explicitly in every model. Never leave these arrays empty or overly permissive. Validate incoming request data before passing it to create or update methods to ensure only intended attributes are modified during bulk operations.

Log failed authentication attempts, privilege escalation events, and suspicious input patterns. Use structured logging with Monolog channels. Integrate with SIEM tools like Datadog or Grafana Loki. Set up alerts for anomalous activity thresholds to enable rapid incident response and forensic analysis.

Update immediately when security releases are announced. Subscribe to Laravel Security Advisories RSS feed. Minor versions often contain critical fixes. Test updates in staging first, but never delay production patching beyond a few days for confirmed vulnerabilities affecting your stack.

Yes, for API routes via throttle middleware. Configure custom limits in bootstrap/app.php for login endpoints and password reset flows to prevent brute force attacks. Adjust values based on your traffic patterns and infrastructure capacity to balance security with legitimate user experience.

Use mimes and max validation rules strictly. Store uploads outside public directory when possible. Generate unique filenames and verify MIME types server-side using finfo, not just client-provided extensions. Scan files with ClamAV before processing to prevent malware execution.

Unserializing untrusted user input allows arbitrary code execution. Never pass request data directly to unserialize(). Use JSON encoding for data transfer instead. If serialization is required, implement allowlists and validate integrity with HMAC signatures before restoring object state from stored or transmitted data.

Combine automated scanning with ZAP or Burp Suite alongside manual penetration testing. Write feature tests for authorization boundaries and input validation edge cases. Review code against OWASP ASVS checklist quarterly. Treat security testing as continuous verification, not a one-time audit checkbox.

Yes. APP_DEBUG=true displays stack traces, environment variables, and database queries in error responses. Always set APP_DEBUG=false in production. Configure custom error pages and log detailed errors server-side only to prevent information disclosure that aids attacker reconnaissance efforts.