Security is not a feature you bolt on at the end, but a habit you apply from the very first line of code. The good news is that Laravel already secures many things by default — as long as you do not turn them off. This article sums up 10 must-do security practices you can apply immediately to a Laravel app, each with real code examples.
1. Turn Off Debug in Production (APP_DEBUG=false)
The most common and most dangerous mistake. When APP_DEBUG=true, every error shows a stack trace containing server paths, code snippets, and even database credentials. In the production .env file, this is mandatory:
APP_ENV=production
APP_DEBUG=false
2. Always Validate User Input
Never trust data from users. Validate every input before processing so dirty or dangerous data is rejected early:
$request->validate([
'email' => 'required|email|max:255',
'age' => 'required|integer|min:1|max:120',
'password' => 'required|min:8|confirmed',
]);
Validation is not just about UX; it is your first line of defense against unexpected data.
3. Enable CSRF Protection
CSRF (Cross-Site Request Forgery) tricks a logged-in user into submitting a request without knowing. Laravel protects against it automatically, but every POST form must include a token:
<form method="POST" action="/profile">
@csrf
<input type="text" name="name">
<button type="submit">Save</button>
</form>
Without @csrf, the request is rejected with a 419 error. Never disable the CSRF middleware just to make a form "work".
4. Prevent Mass Assignment with $fillable
Without restrictions, an attacker could smuggle a field like is_admin through a form. Declare which columns may be mass-assigned:
class User extends Model
{
protected $fillable = ['name', 'email', 'password'];
}
Sensitive columns such as is_admin or role are deliberately left out so they cannot be changed through mass create() or update().
5. Use Query Builder / Eloquent, Avoid Raw SQL
SQL Injection happens when user input is pasted directly into a query. Eloquent and the Query Builder use prepared statements automatically. If you must use raw SQL, use parameter binding, never concatenation:
// DANGEROUS - do not do this
$users = DB::select("SELECT * FROM users WHERE email = '$email'");
// SAFE - use parameter binding
$users = DB::select('SELECT * FROM users WHERE email = ?', [$email]);
// BETTER - use Eloquent
$user = User::where('email', $email)->first();
6. Prevent XSS with Blade Escaping
XSS (Cross-Site Scripting) injects malicious scripts into a page. Blade escapes output automatically with the double curly brace syntax. The problem arises when you use {!! !!} on user data:
{{-- SAFE: escaped automatically --}}
{{ $comment->body }}
{{-- DANGEROUS if it contains user input --}}
{!! $comment->body !!}
Use {!! !!} only for HTML you fully trust, and sanitize it first with a library such as HTML Purifier when needed.
7. Force HTTPS
Without HTTPS, data including passwords is sent as plain text that can be intercepted. After installing an SSL certificate, force all URLs to use HTTPS in AppServiceProvider:
public function boot(): void
{
if ($this->app->environment('production')) {
URL::forceScheme('https');
}
}
8. Apply Rate Limiting
Rate limiting caps the number of requests to prevent brute force and abuse. Apply it to sensitive routes like login or APIs:
Route::middleware('throttle:6,1')->group(function () {
Route::post('/login', [LoginController::class, 'store']);
});
The example above limits 6 attempts per minute — enough to slow a password-guessing attack without disrupting real users.
9. Hash Passwords, Never Store Plain Text
Passwords must never be stored as-is. Laravel uses bcrypt via the Hash helper:
use Illuminate\Support\Facades\Hash;
$user->password = Hash::make($request->password);
// Verify at login
if (Hash::check($request->password, $user->password)) {
// password matches
}
The Hash::make() function produces a one-way hash that differs each time, so a leaked database does not directly expose passwords.
10. Update Dependencies and Audit Regularly
Vulnerabilities often come from outdated third-party packages. Periodically check whether any package has a known security hole:
composer audit
composer update
Running composer audit scans against known security advisories. Apply minor updates regularly, and test with automated tests before shipping them to production.
Conclusion
- Leverage Laravel's built-in protections — do not disable CSRF, escaping, or debug without a strong reason.
- Treat all user input as potentially dangerous until validated.
- Security is layered: closing one hole does not mean you are safe, apply them all.
These ten practices close the most commonly exploited holes in web apps. Making them a habit from the start is far cheaper than patching after a data breach.