Complete Guide to Laravel Security
Security should be a top priority in any web application. Laravel provides robust security features out of the box, but understanding and properly implementing them is crucial. This guide covers essential security practices and advanced techniques to protect your Laravel applications.
Authentication and Authorization
Laravel's authentication system is comprehensive and secure by default, but proper implementation is key:
Multi-Factor Authentication (MFA)
class TwoFactorController extends Controller
{
public function enable(Request $request)
{
$user = $request->user();
$user->two_factor_secret = encrypt(Google2FA::generateSecretKey());
$user->save();
return response()->json([
'qr_code' => Google2FA::getQRCodeUrl(
config('app.name'),
$user->email,
decrypt($user->two_factor_secret)
)
]);
}
}
API Token Management
// Using Laravel Sanctum
$token = $user->createToken('api-token', ['read', 'write']);
// With expiration
$token = $user->createToken('api-token', ['*'], now()->addDays(7));
Protection Against Common Vulnerabilities
SQL Injection Prevention
Always use Eloquent ORM or query builder. When raw queries are necessary, use parameter binding:
// Dangerous
$users = DB::select("SELECT * FROM users WHERE email = '{$email}'");
// Safe
$users = DB::select("SELECT * FROM users WHERE email = ?", [$email]);
// With named bindings
$users = DB::select("SELECT * FROM users WHERE email = :email", ['email' => $email]);
Cross-Site Scripting (XSS) Prevention
Laravel automatically escapes output, but be careful with raw output:
// Automatically escaped
{{ $userInput }}
// Raw output - use with caution
{!! $trustedHtml !!}
// Purify HTML input
use Mews\Purifier\Facades\Purifier;
$cleanHtml = Purifier::clean($request->input('content'));
Cross-Site Request Forgery (CSRF) Protection
Laravel includes CSRF protection by default. For AJAX requests:
// In your layout
// In your JavaScript
axios.defaults.headers.common['X-CSRF-TOKEN'] = document.querySelector('meta[name="csrf-token"]').content;
Advanced Security Measures
Rate Limiting
// In RouteServiceProvider
RateLimiter::for('api', function (Request $request) {
return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
});
// Custom rate limiting
RateLimiter::for('login', function (Request $request) {
return [
Limit::perMinute(5)->by($request->email.$request->ip()),
Limit::perDay(100)->by($request->ip()),
];
});
Content Security Policy (CSP)
class ContentSecurityPolicyMiddleware
{
public function handle($request, Closure $next)
{
$response = $next($request);
$response->headers->set('Content-Security-Policy',
"default-src 'self'; " .
"script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net; " .
"style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; " .
"img-src 'self' data: https:; " .
"font-src 'self' https://fonts.gstatic.com;"
);
return $response;
}
}
Secure File Handling
File Upload Validation
$request->validate([
'document' => [
'required',
'file',
'mimes:pdf,doc,docx',
'max:2048',
function ($attribute, $value, $fail) {
// Additional MIME type verification
$mimeType = $value->getMimeType();
$allowedMimeTypes = ['application/pdf', 'application/msword'];
if (!in_array($mimeType, $allowedMimeTypes)) {
$fail('The file type is not allowed.');
}
},
],
]);
Secure File Storage
// Store files outside public directory
$path = $request->file('document')->store('documents', 'private');
// Secure file serving
Route::get('/documents/{document}', function ($document) {
abort_if(!auth()->check(), 403);
$path = storage_path('app/private/documents/' . $document);
if (!Storage::disk('private')->exists('documents/' . $document)) {
abort(404);
}
return response()->file($path);
})->middleware('auth');
Environment and Configuration Security
Environment Variables
- Never commit .env files to version control
- Use strong, unique APP_KEY
- Set APP_DEBUG=false in production
- Use environment-specific configurations
Configuration Caching
# Cache configuration in production
php artisan config:cache
php artisan route:cache
php artisan view:cache
Database Security
Encryption at Rest
class User extends Model
{
protected $casts = [
'ssn' => 'encrypted',
'credit_card' => 'encrypted:array',
];
}
Query Logging in Production
// Disable query logging in production
if (app()->environment('production')) {
DB::disableQueryLog();
}
Security Headers
class SecurityHeadersMiddleware
{
public function handle($request, Closure $next)
{
$response = $next($request);
$response->headers->set('X-Frame-Options', 'SAMEORIGIN');
$response->headers->set('X-Content-Type-Options', 'nosniff');
$response->headers->set('X-XSS-Protection', '1; mode=block');
$response->headers->set('Referrer-Policy', 'strict-origin-when-cross-origin');
$response->headers->set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
return $response;
}
}
Monitoring and Auditing
Activity Logging
class LogUserActivity
{
public function handle($request, Closure $next)
{
$response = $next($request);
if (auth()->check()) {
Activity::create([
'user_id' => auth()->id(),
'action' => $request->method() . ' ' . $request->path(),
'ip_address' => $request->ip(),
'user_agent' => $request->userAgent(),
]);
}
return $response;
}
}
Best Practices Checklist
- ✅ Keep Laravel and packages updated
- ✅ Use HTTPS everywhere
- ✅ Implement proper authentication and authorization
- ✅ Validate and sanitize all input
- ✅ Use prepared statements for database queries
- ✅ Implement rate limiting
- ✅ Set security headers
- ✅ Encrypt sensitive data
- ✅ Regular security audits
- ✅ Monitor and log suspicious activities
Conclusion
Security is an ongoing process, not a one-time setup. By implementing these practices and staying informed about new vulnerabilities and patches, you can maintain a robust security posture for your Laravel applications. Regular security audits and updates are essential for long-term application security.