Who This Guide Is For
This guide is for Laravel developers building or maintaining applications that handle real user data in production. You understand the framework basics and want to ensure your application is not vulnerable to the attacks that compromise most web applications: injection, broken authentication, insecure configuration, and data exposure.
Before You Start
You should have a working Laravel application and familiarity with Laravel’s authentication, middleware, and validation systems. This guide covers hardening an existing application, not building one from scratch. If you are starting a new project, apply these patterns from the beginning. Retrofitting security is always harder than building it in.
Step 1: Harden Authentication
Laravel’s built-in authentication is solid, but the defaults leave gaps in production. Review and tighten these areas:
Password hashing: Laravel uses bcrypt by default, which is appropriate. Verify that the cost factor is at least 12 (the default is 10). Higher cost factors make brute-force attacks slower at the expense of slightly longer login times. For applications with high-value accounts, consider switching to Argon2id.
Session configuration: set the session lifetime to the minimum acceptable for your use case (120 minutes is typical for business applications). Enable the expire_on_close option for sensitive applications. Use the database or Redis session driver in production. The file driver creates issues with load-balanced environments and does not support concurrent request handling cleanly.
Rate limiting on login: apply rate limiting to login endpoints to prevent brute-force attacks. Laravel’s built-in throttle middleware works, but configure it specifically for authentication routes rather than relying on the global throttle. Five attempts per minute per IP is a reasonable starting point.
Two-factor authentication: for admin accounts and high-privilege users, implement TOTP-based two-factor authentication. Laravel Fortify provides this out of the box. At minimum, enforce it for any account that can modify user data, access billing, or change system configuration.
Step 2: Validate All Input Rigorously
Every piece of data that enters your application from an external source (request parameters, headers, uploaded files, webhook payloads) must be validated before use. Laravel’s validation system handles this well, but the discipline of applying it consistently is what prevents vulnerabilities.
Form requests: use dedicated form request classes for every controller action that accepts input. This centralises validation logic and ensures it cannot be accidentally bypassed. Avoid inline validation in controllers, where it is easy to forget or skip.
Type-specific validation: validate not just that a field exists but that it is the correct type and within expected bounds. An email field should be validated as an email. A numeric ID should be validated as an integer within a realistic range. A date should be validated as a date in the expected format. Generic “required” validation is not enough.
SQL injection prevention: use Eloquent and the query builder exclusively. Never concatenate user input into raw SQL. If you must use raw queries (DB::raw or DB::select), use parameterised bindings for every user-supplied value. The one raw query without bindings is the one that gets exploited.
XSS prevention: Laravel’s Blade templating escapes output by default with the double-curly-brace syntax. Never use the unescaped syntax unless you have explicitly sanitised the content. For user-generated HTML content, use a sanitisation library that whitelist-filters allowed tags and attributes.
Step 3: Configure Security Headers and CSRF
CSRF protection: Laravel’s CSRF middleware is enabled by default. Verify it is applied to all state-changing routes. For API routes using token authentication, CSRF middleware is not needed (the token itself provides the protection), but for session-based web routes it must remain active.
Security headers: add security headers via middleware. At minimum: X-Content-Type-Options (nosniff), X-Frame-Options (DENY or SAMEORIGIN), X-XSS-Protection set to 0 (the browser feature is deprecated, but the header stops older browsers from enabling their own buggy implementations), and Strict-Transport-Security (max-age=31536000; includeSubDomains) for HTTPS enforcement.
Content Security Policy: implement a CSP header that restricts where scripts, styles, and other resources can be loaded from. A strict CSP is the most effective defence against XSS attacks. Start with a report-only policy to identify violations, then enforce once the policy is clean.
CORS configuration: if your application serves API endpoints consumed by other domains, configure CORS explicitly. The default “allow everything” configuration is a security risk. Specify exactly which origins, methods, and headers are allowed.
Step 4: Secure Data at Rest and in Transit
HTTPS everywhere: force HTTPS on all routes in production. Set the APP_URL to an HTTPS URL and enable the ForceScheme middleware or set the trusted proxies configuration if you are behind a load balancer. Redirect all HTTP traffic to HTTPS at the web server level.
Sensitive data encryption: use Laravel’s encryption (Crypt facade or encrypted model casts) for sensitive data stored in the database: API keys, personal identification numbers, financial account details. Encryption at the application level protects data even if the database is compromised.
Environment configuration: never commit the .env file to version control. Use environment variables on the server for production credentials. Verify that APP_DEBUG is false in production. Debug mode leaks stack traces, database credentials, and environment variables to anyone who triggers an error.
Logging: ensure logs do not contain sensitive data. Passwords, API keys, and personal data should be redacted from log output. Review your exception handler to confirm it does not log full request payloads for routes that handle sensitive input.
Step 5: Audit and Maintain
Dependency scanning: run a vulnerability scanner against your Composer dependencies regularly. Tools like the Symfony security checker or GitHub’s Dependabot flag known vulnerabilities in packages you depend on. Apply security patches within a week of disclosure.
Permission auditing: review which users have which roles and permissions quarterly. Check for permission creep: users who have accumulated access beyond what their current role requires. Spatie Laravel Permission makes this manageable with its role and permission model.
Error handling: configure a production error tracking service (Sentry, Flare, or similar) that captures exceptions with context but does not expose them to users. The user should see a generic error page. The development team should see the full stack trace, request data, and user context in the error tracker.
Regular updates: keep Laravel and all packages updated. The framework team issues security releases that should be applied promptly. Minor version updates within a release line are typically safe and include security fixes.
Common Mistakes
- APP_DEBUG=true in production. This single misconfiguration exposes your entire application internals to anyone who triggers an error. Verify it is false on every deployment.
- Raw queries with concatenated input. Even one unparameterised query is a SQL injection vulnerability. Use bindings for every user-supplied value in raw queries.
- No rate limiting on authentication. Without rate limiting, login forms are vulnerable to credential stuffing and brute-force attacks. Apply throttling specifically to auth routes.
- Logging sensitive data. Check your logs for passwords, tokens, and personal data. Exception handlers and request logging often capture more than they should.
- Ignoring dependency vulnerabilities. A vulnerable package in your dependencies is a vulnerability in your application. Scan and update regularly.
What Good Looks Like
A well-secured Laravel application has: authentication hardened with rate limiting and optional 2FA, all input validated through form requests, CSRF and security headers configured, HTTPS enforced, sensitive data encrypted at rest, debug mode off in production, dependencies scanned for vulnerabilities, and error tracking that captures issues without exposing them to users. Security is a baseline. Every other feature builds on it.
Next Steps
For CI/CD that enforces these security patterns automatically, How to Set Up CI/CD for a Laravel Project covers the pipeline. For file upload security specifically, How to Handle File Uploads Securely goes deeper on that attack surface. For the service engagement, see Laravel Development. Other security and deployment topics are covered in the broader technical guides.
Written by
Alex
CEO
I’m a software developer and CEO of Digital Royalty, helping growing teams scale their SaaS platforms without losing quality, visibility, or control. I focus on building structured, maintainable systems with clear processes, reporting, and accountability. With over a decade of experience across agency and in-house roles, I specialise in delivering long-term, scalable solutions that support complex, evolving products.