Laravel Security Best Practices
Comprehensive security guidance for Laravel applications to protect against common vulnerabilities.
When to Activate
- Adding authentication or authorization
- Handling user input and file uploads
- Building new API endpoints
- Managing secrets and environment settings
- Hardening production deployments
How It Works
- Middleware provides baseline protections (CSRF via
VerifyCsrfToken, security headers via SecurityHeaders).
- Guards and policies enforce access control (
auth:sanctum, $this->authorize, policy middleware).
- Form Requests validate and shape input (
UploadInvoiceRequest) before it reaches services.
- Rate limiting adds abuse protection (
RateLimiter::for('login')) alongside auth controls.
- Data safety comes from encrypted casts, mass-assignment guards, and signed routes (
URL::temporarySignedRoute + signed middleware).
Core Security Settings
APP_DEBUG=false in production
APP_KEY must be set and rotated on compromise
- Set
SESSION_SECURE_COOKIE=true and SESSION_SAME_SITE=lax (or strict for sensitive apps)
- Configure trusted proxies for correct HTTPS detection
Session and Cookie Hardening
- Set
SESSION_HTTP_ONLY=true to prevent JavaScript access
- Use
SESSION_SAME_SITE=strict for high-risk flows
- Regenerate sessions on login and privilege changes
Authentication and Tokens
- Use Laravel Sanctum or Passport for API auth
- Prefer short-lived tokens with refresh flows for sensitive data
- Revoke tokens on logout and compromised accounts
Example route protection:
php
1use Illuminate\Http\Request;
2use Illuminate\Support\Facades\Route;
3
4Route::middleware('auth:sanctum')->get('/me', function (Request $request) {
5 return $request->user();
6});
Password Security
- Hash passwords with
Hash::make() and never store plaintext
- Use Laravel's password broker for reset flows
php
1use Illuminate\Support\Facades\Hash;
2use Illuminate\Validation\Rules\Password;
3
4$validated = $request->validate([
5 'password' => ['required', 'string', Password::min(12)->letters()->mixedCase()->numbers()->symbols()],
6]);
7
8$user->update(['password' => Hash::make($validated['password'])]);
Authorization: Policies and Gates
- Use policies for model-level authorization
- Enforce authorization in controllers and services
php
1$this->authorize('update', $project);
Use policy middleware for route-level enforcement:
php
1use Illuminate\Support\Facades\Route;
2
3Route::put('/projects/{project}', [ProjectController::class, 'update'])
4 ->middleware(['auth:sanctum', 'can:update,project']);
Validation and Data Sanitization
- Always validate inputs with Form Requests
- Use strict validation rules and type checks
- Never trust request payloads for derived fields
Mass Assignment Protection
- Use
$fillable or $guarded and avoid Model::unguard()
- Prefer DTOs or explicit attribute mapping
SQL Injection Prevention
- Use Eloquent or query builder parameter binding
- Avoid raw SQL unless strictly necessary
php
1DB::select('select * from users where email = ?', [$email]);
XSS Prevention
- Blade escapes output by default (
{{ }})
- Use
{!! !!} only for trusted, sanitized HTML
- Sanitize rich text with a dedicated library
CSRF Protection
- Keep
VerifyCsrfToken middleware enabled
- Include
@csrf in forms and send XSRF tokens for SPA requests
For SPA authentication with Sanctum, ensure stateful requests are configured:
php
1// config/sanctum.php
2'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', 'localhost')),
File Upload Safety
- Validate file size, MIME type, and extension
- Store uploads outside the public path when possible
- Scan files for malware if required
php
1final class UploadInvoiceRequest extends FormRequest
2{
3 public function authorize(): bool
4 {
5 return (bool) $this->user()?->can('upload-invoice');
6 }
7
8 public function rules(): array
9 {
10 return [
11 'invoice' => ['required', 'file', 'mimes:pdf', 'max:5120'],
12 ];
13 }
14}
php
1$path = $request->file('invoice')->store(
2 'invoices',
3 config('filesystems.private_disk', 'local') // set this to a non-public disk
4);
Rate Limiting
- Apply
throttle middleware on auth and write endpoints
- Use stricter limits for login, password reset, and OTP
php
1use Illuminate\Cache\RateLimiting\Limit;
2use Illuminate\Http\Request;
3use Illuminate\Support\Facades\RateLimiter;
4
5RateLimiter::for('login', function (Request $request) {
6 return [
7 Limit::perMinute(5)->by($request->ip()),
8 Limit::perMinute(5)->by(strtolower((string) $request->input('email'))),
9 ];
10});
Secrets and Credentials
- Never commit secrets to source control
- Use environment variables and secret managers
- Rotate keys after exposure and invalidate sessions
Encrypted Attributes
Use encrypted casts for sensitive columns at rest.
php
1protected $casts = [
2 'api_token' => 'encrypted',
3];
Security Headers
- Add CSP, HSTS, and frame protection where appropriate
- Use trusted proxy configuration to enforce HTTPS redirects
Example middleware to set headers:
php
1use Illuminate\Http\Request;
2use Symfony\Component\HttpFoundation\Response;
3
4final class SecurityHeaders
5{
6 public function handle(Request $request, \Closure $next): Response
7 {
8 $response = $next($request);
9
10 $response->headers->add([
11 'Content-Security-Policy' => "default-src 'self'",
12 'Strict-Transport-Security' => 'max-age=31536000', // add includeSubDomains/preload only when all subdomains are HTTPS
13 'X-Frame-Options' => 'DENY',
14 'X-Content-Type-Options' => 'nosniff',
15 'Referrer-Policy' => 'no-referrer',
16 ]);
17
18 return $response;
19 }
20}
CORS and API Exposure
- Restrict origins in
config/cors.php
- Avoid wildcard origins for authenticated routes
php
1// config/cors.php
2return [
3 'paths' => ['api/*', 'sanctum/csrf-cookie'],
4 'allowed_methods' => ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],
5 'allowed_origins' => ['https://app.example.com'],
6 'allowed_headers' => [
7 'Content-Type',
8 'Authorization',
9 'X-Requested-With',
10 'X-XSRF-TOKEN',
11 'X-CSRF-TOKEN',
12 ],
13 'supports_credentials' => true,
14];
Logging and PII
- Never log passwords, tokens, or full card data
- Redact sensitive fields in structured logs
php
1use Illuminate\Support\Facades\Log;
2
3Log::info('User updated profile', [
4 'user_id' => $user->id,
5 'email' => '[REDACTED]',
6 'token' => '[REDACTED]',
7]);
Dependency Security
- Run
composer audit regularly
- Pin dependencies with care and update promptly on CVEs
Signed URLs
Use signed routes for temporary, tamper-proof links.
php
1use Illuminate\Support\Facades\URL;
2
3$url = URL::temporarySignedRoute(
4 'downloads.invoice',
5 now()->addMinutes(15),
6 ['invoice' => $invoice->id]
7);
php
1use Illuminate\Support\Facades\Route;
2
3Route::get('/invoices/{invoice}/download', [InvoiceController::class, 'download'])
4 ->name('downloads.invoice')
5 ->middleware('signed');