Security vulnerabilities in web applications make headlines regularly. Data breaches damage reputations, invite lawsuits, and destroy user trust. Laravel provides excellent security foundations, but they only work if you use them correctly. Here's how to secure your Laravel application against common attacks.
Security as a Foundation
Security isn't a feature you add at the end;it's a mindset that influences every decision. The cost of addressing security early is minimal. The cost of a breach is enormous.
This guide covers the most common vulnerabilities and Laravel's built-in protections. Following these practices won't make your application invulnerable, but it will protect against the attacks that compromise most applications.
Authentication Best Practices
Authentication verifies identity. Get it wrong, and attackers access your users' accounts.
Password Handling
Never store passwords in plain text. Laravel uses bcrypt by default, which is excellent. Don't change it to something faster;password hashing should be slow to resist brute-force attacks.
// Laravel handles this automatically with User model
$user = User::create([
'password' => Hash::make($request->password), // or just pass plain, it's auto-hashed
]);
Enforce minimum password requirements:
$request->validate([
'password' => ['required', 'min:12', 'confirmed', Password::defaults()],
]);
Laravel's Password::defaults() rule enforces reasonable complexity. Configure it in a service provider:
Password::defaults(function () {
return Password::min(12)
->mixedCase()
->numbers()
->uncompromised(); // Checks against known breached passwords
});
Multi-Factor Authentication
Passwords alone are insufficient for sensitive applications. Implement MFA using Laravel Fortify or a package like pragmarx/google2fa-laravel:
// Generate secret for user
$secret = Google2FA::generateSecretKey();
// Verify code during login
if (Google2FA::verifyKey($user->google2fa_secret, $request->otp)) {
// Code is valid
}
Session Security
Configure sessions securely in config/session.php:
'secure' => true, // Only send over HTTPS
'http_only' => true, // Prevent JavaScript access
'same_site' => 'lax', // Mitigate CSRF attacks
Regenerate session IDs after authentication to prevent session fixation:
// Laravel does this automatically in the authentication flow
$request->session()->regenerate();
Authorization with Policies and Gates
Authentication asks "who are you?" Authorization asks "what can you do?"
Define policies for each model:
class ProjectPolicy
{
public function view(User $user, Project $project): bool
{
return $user->id === $project->user_id
|| $user->belongsToTeam($project->team_id);
}
public function delete(User $user, Project $project): bool
{
return $user->id === $project->user_id;
}
}
Use policies in controllers:
public function show(Project $project)
{
$this->authorize('view', $project);
return view('projects.show', compact('project'));
}
Never assume authorization based on URL obscurity. Just because someone can't guess a URL doesn't mean they're authorized. Always check permissions explicitly.
Input Validation and Sanitization
Never trust user input. Validate everything that comes from outside your application.
Use Form Requests for validation:
class StoreProjectRequest extends FormRequest
{
public function rules(): array
{
return [
'name' => ['required', 'string', 'max:255'],
'description' => ['nullable', 'string', 'max:5000'],
'budget' => ['required', 'numeric', 'min:0', 'max:999999.99'],
'client_id' => ['required', 'exists:clients,id'],
];
}
}
Sanitize output when displaying user-generated content:
{{-- This is escaped by default - XSS safe --}}
{{ $project->name }}
{{-- This is NOT escaped - dangerous with user input --}}
{!! $project->description !!}
Only use {!! !!} when you've explicitly sanitized the content or it's from a trusted source.
CSRF Protection
Cross-Site Request Forgery tricks users into submitting unintended requests. Laravel protects against this automatically.
Include the CSRF token in forms:
<form method="POST" action="/projects">
@csrf
<!-- form fields -->
</form>
For AJAX requests, include the token in headers:
axios.defaults.headers.common['X-CSRF-TOKEN'] = document
.querySelector('meta[name="csrf-token"]')
.getAttribute('content');
Don't disable CSRF protection. If you're tempted to disable it because something isn't working, find the real solution instead.
SQL Injection Prevention
SQL injection lets attackers execute arbitrary database queries. Laravel's query builder and Eloquent protect against this when used correctly.
Safe (parameterized):
User::where('email', $request->email)->first();
DB::table('users')->where('email', '=', $request->email)->first();
Dangerous (raw SQL with concatenation):
// NEVER DO THIS
DB::select("SELECT * FROM users WHERE email = '" . $request->email . "'");
When raw SQL is necessary, use parameter binding:
DB::select('SELECT * FROM users WHERE email = ?', [$request->email]);
XSS Protection
Cross-Site Scripting injects malicious scripts into pages viewed by other users.
Blade's {{ }} syntax escapes output by default. This is your primary defense:
{{-- Safe: HTML entities are escaped --}}
<h1>{{ $project->name }}</h1>
Configure Content Security Policy headers to limit what scripts can execute:
// In middleware or config
return $response->withHeaders([
'Content-Security-Policy' => "default-src 'self'; script-src 'self'",
]);
Security Headers
HTTP security headers instruct browsers to enable additional protections:
// Add to middleware
return $next($request)->withHeaders([
'X-Content-Type-Options' => 'nosniff',
'X-Frame-Options' => 'DENY',
'X-XSS-Protection' => '1; mode=block',
'Strict-Transport-Security' => 'max-age=31536000; includeSubDomains',
'Referrer-Policy' => 'strict-origin-when-cross-origin',
]);
Or use a package like spatie/laravel-csp for comprehensive Content Security Policy management.
HTTPS Everywhere
Always use HTTPS in production. Force HTTPS redirects:
// In AppServiceProvider boot method
if (app()->environment('production')) {
URL::forceScheme('https');
}
Configure your web server to redirect HTTP to HTTPS at the server level for better performance.
File Upload Security
File uploads are a common attack vector. Validate thoroughly:
$request->validate([
'document' => [
'required',
'file',
'mimes:pdf,doc,docx',
'max:10240', // 10MB
],
]);
Store uploads outside the web root and serve them through a controller that checks authorization:
public function download(Document $document)
{
$this->authorize('download', $document);
return Storage::download($document->path, $document->original_name);
}
Never execute uploaded files or trust user-provided filenames.
Security Audits Checklist
Regularly review your application's security:
- Dependencies are up to date (
composer audit) - Debug mode is disabled in production
- Error details are not exposed to users
- Sensitive data is encrypted at rest
- Database credentials are not in version control
- Admin routes require authentication and authorization
- Rate limiting is in place for authentication endpoints
- Logs don't contain sensitive data (passwords, tokens)
- Old user sessions are invalidated on password change
- Two-factor authentication is available for sensitive accounts
Conclusion
Laravel provides robust security features, but they require correct implementation. The framework can't protect you from disabling CSRF protection, using raw queries with user input, or forgetting to check authorization.
Security is an ongoing practice. Stay updated on Laravel security releases. Subscribe to security mailing lists. Periodically audit your application. The effort you invest in security protects your users and your business from increasingly sophisticated attacks.
Most security breaches exploit known vulnerabilities with known solutions. By following established practices, you defend against the vast majority of attacks. Perfect security doesn't exist, but good security is achievable with consistent attention.