Laravel Security Best Practices 2026: CSRF, Sanctum, Eloquent & Headers
Laravel security is a first-class concern in the framework — but many teams rely on defaults without fully understanding what they protect and where gaps remain. This guide walks through every critical layer of a production Laravel application, from request validation to authentication tokens.
Before diving in, get an instant picture of your current security posture with a free scan at ZeriFlow — 80+ checks in under a minute.
1. CSRF Protection in Laravel
Laravel includes CSRF protection out of the box via VerifyCsrfToken middleware, applied globally to all web routes. For every form, use the @csrf Blade directive.
<form method='POST' action='/submit'>
@csrf
<input type='text' name='email'>
<button type='submit'>Submit</button>
</form>For API routes using Sanctum, CSRF protection is handled differently — you must first call /sanctum/csrf-cookie before making state-changing requests from your SPA:
await axios.get('/sanctum/csrf-cookie');
await axios.post('/login', { email, password });If you need to exclude specific routes from CSRF (e.g., webhook endpoints), do so explicitly and only for those routes:
// app/Http/Middleware/VerifyCsrfToken.php
protected $except = [
'stripe/webhook',
'github/webhook',
];2. Eloquent and Mass Assignment Protection
Mass assignment vulnerabilities occur when a model accepts arbitrary user-supplied fields. Laravel's $fillable and $guarded properties prevent this.
Use $fillable (whitelist approach — preferred):
class User extends Model
{
protected $fillable = ['name', 'email', 'password'];
// Only these fields can be mass-assigned
}Never use this in production:
protected $guarded = []; // Allows ALL fields — dangerousAlways validate incoming requests with Form Requests before passing data to Eloquent:
class StoreUserRequest extends FormRequest
{
public function rules(): array
{
return [
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'email', 'unique:users'],
'password' => ['required', 'min:12', 'confirmed'],
];
}
}
// In your controller
public function store(StoreUserRequest $request)
{
$user = User::create($request->validated()); // Only validated, fillable fields
}3. Laravel Sanctum for API Authentication
Sanctum provides lightweight token-based authentication for SPAs and mobile apps. It is preferred over Passport for most applications.
Installation:
composer require laravel/sanctum
php artisan vendor:publish --provider='Laravel\Sanctum\SanctumServiceProvider'
php artisan migrateIssuing tokens with expiry:
$token = $user->createToken('mobile-app', ['read', 'write'], now()->addDays(30));
return response()->json(['token' => $token->plainTextToken]);Token scopes in middleware:
Route::middleware(['auth:sanctum', 'ability:read'])->group(function () {
Route::get('/profile', [ProfileController::class, 'show']);
});Revoking tokens on logout:
public function logout(Request $request)
{
$request->user()->currentAccessToken()->delete();
return response()->noContent();
}Set SANCTUM_STATEFUL_DOMAINS in your .env to restrict which domains can use cookie-based session authentication for your SPA.
4. Securing the .env File and Secrets
Your .env file is the most sensitive file in a Laravel project. These rules are non-negotiable.
- Never commit
.envto version control — ensure.envis in.gitignore - Use environment-specific files:
.env.production,.env.staging - Store production secrets in a secrets manager (AWS Secrets Manager, Vault, Doppler)
- Generate a new
APP_KEYfor each environment:php artisan key:generate - Set
APP_DEBUG=falseandAPP_ENV=productionin production - Remove the
/telescopeand/horizonroutes from public access in production
Check for exposed .env files with ZeriFlow — it specifically tests whether .env, .git/config, and similar sensitive files are publicly accessible. Run a free scan.
// config/app.php — ensure debug is driven by env
'debug' => (bool) env('APP_DEBUG', false),5. Security Headers Middleware
Laravel does not set security headers by default. Add a middleware to apply them globally.
// app/Http/Middleware/SecurityHeaders.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', 'DENY');
$response->headers->set('Referrer-Policy', 'strict-origin-when-cross-origin');
$response->headers->set('Permissions-Policy', 'geolocation=(), camera=(), microphone=()');
$response->headers->set(
'Content-Security-Policy',
"default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'"
);
if ($request->secure()) {
$response->headers->set(
'Strict-Transport-Security',
'max-age=31536000; includeSubDomains; preload'
);
}
return $response;
}
}Register it globally in bootstrap/app.php (Laravel 11+):
->withMiddleware(function (Middleware $middleware) {
$middleware->append(\App\Http\Middleware\SecurityHeaders::class);
})6. Dependency Auditing and Brakeman Equivalent
Laravel does not have a direct equivalent to Brakeman, but composer audit checks your dependencies against known CVEs.
composer auditFor static analysis of your application code, use Larastan (PHPStan for Laravel):
composer require --dev larastan/larastan
./vendor/bin/phpstan analyse --level=6 appAlso consider using Enlightn, which performs a comprehensive security and performance audit of your Laravel application:
composer require --dev enlightn/enlightn
php artisan enlightnFAQ
### Q: Is Laravel's built-in query builder safe from SQL injection?
A: Yes, when used correctly. The query builder and Eloquent use PDO prepared statements internally. The risk comes from using whereRaw(), selectRaw(), or DB::statement() with user input. Always pass bindings as the second argument: ->whereRaw('email = ?', [$email]).
### Q: What is the difference between Sanctum and Passport? A: Passport implements the full OAuth 2.0 server specification, which is heavy and complex. Sanctum is a lighter alternative for SPAs and mobile apps that do not need full OAuth flows. Use Sanctum unless you specifically need to issue tokens to third-party applications.
### Q: How should I handle file uploads securely in Laravel?
A: Validate MIME type (not just extension), restrict to expected types, store files outside the webroot using Storage::disk('private'), generate UUID filenames, and never trust $_FILES['type']. Use $request->file('photo')->getMimeType() after upload.
### Q: Should I disable the Laravel debug bar in production?
A: Yes, absolutely. The debug bar exposes queries, session data, and environment variables to anyone with browser dev tools. Set DEBUGBAR_ENABLED=false in production and ensure APP_DEBUG=false.
### Q: Is rate limiting built into Laravel?
A: Yes. The throttle middleware applies rate limiting to route groups. For APIs, use RateLimiter::for() in AppServiceProvider to define named limiters with dynamic limits per user.
Conclusion
Laravel security in 2026 means using every layer the framework provides: CSRF tokens on all forms, $fillable on every model, Sanctum for stateless API auth, a hardened .env, and security headers on every response. None of these require external services — just discipline.
Close the loop by scanning your live application from the outside. Run a free ZeriFlow scan to detect exposed secrets, missing headers, and TLS misconfigurations that code review alone cannot catch.