Most Laravel applications are secure by default until someone changes a default. The framework ships with sensible protections, but the way an app is configured, deployed and extended is where the real risk lives. This is the checklist I work through before I sign off on a Laravel application going to production. It is deliberately practical: every item is something I have seen go wrong on a real review, and every fix is a few lines of config or code.
Work through it top to bottom. If you want the reasoning behind each area in more depth, the Laravel Security Review hub links the deeper guides.
1. Lock down environment and configuration
Your .env file and app config decide how much an attacker learns when something breaks.
- Set
APP_DEBUG=falseandAPP_ENV=production. Debug mode renders full stack traces, environment variables and query contents to any visitor who triggers an error. - Set a real
APP_KEY(php artisan key:generate) and never reuse it across environments. It encrypts sessions and cookies. - Keep
.envout of version control and out of the web root. Confirmhttps://yoursite/.envreturns 404, not the file. - Run
php artisan config:cacheso config is read from a compiled file, not re-parsed per request.
2. Force HTTPS and set security headers
Transport security is theory until it is enforced on every response.
// app/Providers/AppServiceProvider.php
public function boot(): void
{
if ($this->app->environment('production')) {
\URL::forceScheme('https');
}
}
Add Strict-Transport-Security, X-Content-Type-Options: nosniff, X-Frame-Options and a Content-Security-Policy through middleware or your web server. A restrictive CSP is the single most effective defence against cross-site scripting.
3. Verify authentication and session settings
Authentication is where a small default becomes a large hole.
SESSION_SECURE_COOKIE=trueandSESSION_HTTP_ONLY=trueso session cookies never travel over HTTP or reach JavaScript.- Set
SESSION_SAME_SITE=lax(orstrictfor sensitive apps). - Throttle login routes so credential stuffing is expensive. A single line of middleware does it:
Route::post('/login', ...)->middleware('throttle:5,1'); - Confirm passwords are hashed with bcrypt or argon2 (Laravel’s default) and never with
md5orsha1from a legacy import.
The full treatment lives in Securing Authentication and Authorization in Laravel.
4. Guard the database layer
Eloquent parameterises queries for you, so SQL injection only reappears when you step outside it.
- Never interpolate user input into
DB::raw()orwhereRaw(). Use bindings:whereRaw('votes = ?', [$count]). - Protect against mass assignment. Define
$fillableexplicitly rather than leaving a model wide open with$guarded = []. - Give the production database user only the privileges the app needs. It rarely needs
DROPorGRANT.
5. Validate every input and escape every output
Validation is not a formality; it is your allow-list of what the application accepts.
$validated = $request->validate([
'email' => 'required|email',
'age' => 'required|integer|min:0|max:150',
'role' => 'required|in:member,editor',
]);
On output, trust Blade’s {{ $value }} escaping and treat {!! $value !!} as a red flag to be justified case by case. For the reasoning behind tokens and request forgery, see Why do we use a token concept in PHP?.
6. Handle file uploads defensively
Uploads are the classic path from “user input” to “code execution.”
- Validate with rules that check the real type:
'document' => 'required|file|mimes:pdf,docx|max:5120'. - Store uploads outside the web root, or on a dedicated disk, and never trust the original filename.
- Serve user files through a controller that sets the content type, not by linking straight to the stored path.
The fundamentals carry over from file upload handling in PHP.
7. Audit dependencies before every release
Most breaches I investigate enter through an unpatched package, not custom code.
composer audit
composer outdated --direct
Pin versions, review the changelog of security releases, and update promptly. A vulnerable dependency two days after its patch is exactly how sites get compromised.
8. Control what errors and logs reveal
- Confirm production returns a generic error page, never a stack trace.
- Send logs somewhere durable, and scrub secrets and tokens before they are written.
- Log authentication events (logins, failures, password changes) so you can reconstruct an incident.
Where to go next
This checklist is the spine of a review. Two companion guides go deeper on the areas that carry the most risk: authentication and authorization, and preventing the OWASP Top 10 in Laravel. For the framework’s built-in protections, start with 7 Laravel features to enhance the security of your application.
Related
- Laravel Security Review: a practical walk through reviewing and hardening a Laravel application, area by area, with every guide in this cluster in one place.
- Securing Authentication and Authorization in Laravel
- Preventing the OWASP Top 10 in Laravel
- 7 Laravel Features to Enhance the Security of Your Application