Skip to main content
Back to blog
April 28, 2026|8 min read

Laravel Security Best Practices 2026: CSRF, Sanctum, Eloquent & Headers

Laravel security is layered across the framework, from Eloquent's mass-assignment protection to Sanctum's token management. This guide shows you how to use every layer correctly.

ZeriFlow Team

1,093 words

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.

blade
<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:

javascript
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:

php
// 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):

php
class User extends Model
{
    protected $fillable = ['name', 'email', 'password'];
    // Only these fields can be mass-assigned
}

Never use this in production:

php
protected $guarded = []; // Allows ALL fields — dangerous

Always validate incoming requests with Form Requests before passing data to Eloquent:

php
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:

bash
composer require laravel/sanctum
php artisan vendor:publish --provider='Laravel\Sanctum\SanctumServiceProvider'
php artisan migrate

Issuing tokens with expiry:

php
$token = $user->createToken('mobile-app', ['read', 'write'], now()->addDays(30));
return response()->json(['token' => $token->plainTextToken]);

Token scopes in middleware:

php
Route::middleware(['auth:sanctum', 'ability:read'])->group(function () {
    Route::get('/profile', [ProfileController::class, 'show']);
});

Revoking tokens on logout:

php
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 .env to version control — ensure .env is 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_KEY for each environment: php artisan key:generate
  • Set APP_DEBUG=false and APP_ENV=production in production
  • Remove the /telescope and /horizon routes 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.

php
// 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.

php
// 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+):

php
->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.

bash
composer audit

For static analysis of your application code, use Larastan (PHPStan for Laravel):

bash
composer require --dev larastan/larastan
./vendor/bin/phpstan analyse --level=6 app

Also consider using Enlightn, which performs a comprehensive security and performance audit of your Laravel application:

bash
composer require --dev enlightn/enlightn
php artisan enlightn

FAQ

### 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.

Ready to check your site?

Run a free security scan in 30 seconds.

Related articles

Keep reading