Laravel 13 Security: What Changed from Laravel 12 and What You Need to Know
A security-focused review of Laravel 13 for teams upgrading from Laravel 12. Covers new defaults, deprecated patterns, configuration changes, and a post-upgrade security checklist.
Laravel 13 has arrived, and as with every major version, the security landscape shifts. Some changes strengthen your application's posture automatically. Others can introduce vulnerabilities if you upgrade without reviewing the implications.
This guide focuses exclusively on the security-relevant changes between Laravel 12 and Laravel 13. We cover new defaults, deprecated patterns, configuration changes, and a concrete checklist for verifying your security posture after upgrading.
Understanding Major Version Security Changes
Every major Laravel release follows a pattern: security defaults get stricter, deprecated methods get removed, and new features introduce new configuration surfaces. Laravel 13 continues this trend.
The risk during upgrades is not the new features themselves. It is the gap between what your application expects and what the framework now provides. A changed default value, a removed method, or a new middleware behavior can create vulnerabilities if your code assumes the old behavior.
Encryption Changes
Default Cipher Update
Laravel 13 adopts AES-256-GCM as the default encryption cipher, replacing AES-256-CBC that served as the default in previous versions. GCM (Galois/Counter Mode) provides authenticated encryption, meaning it verifies both the confidentiality and integrity of encrypted data in a single operation.
With CBC, Laravel had to separately compute an HMAC to verify integrity. GCM handles both in one pass, which is both faster and eliminates a class of padding oracle attacks.
What you need to do:
If you have existing encrypted data (database fields encrypted with Crypt::encrypt(), encrypted cookies, etc.), you must handle the cipher transition carefully:
// Option 1: Keep the old cipher explicitly in config/app.php
// This maintains backward compatibility with existing encrypted data
'cipher' => 'AES-256-CBC',
// Option 2: Migrate encrypted data to the new cipher
// Run this as a one-time migration after upgrading
Here is a migration approach for re-encrypting stored data:
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Crypt;
return new class extends Migration
{
public function up(): void
{
// Temporarily use the old cipher to decrypt
config(['app.cipher' => 'AES-256-CBC']);
$records = DB::table('users')
->whereNotNull('encrypted_ssn')
->cursor();
foreach ($records as $record) {
try {
$decrypted = Crypt::decryptString($record->encrypted_ssn);
// Switch to new cipher for re-encryption
config(['app.cipher' => 'AES-256-GCM']);
$reEncrypted = Crypt::encryptString($decrypted);
DB::table('users')
->where('id', $record->id)
->update(['encrypted_ssn' => $reEncrypted]);
// Switch back for next iteration
config(['app.cipher' => 'AES-256-CBC']);
} catch (\Exception $e) {
logger()->error("Failed to re-encrypt user {$record->id}: {$e->getMessage()}");
}
}
}
};
Warning: Never change the cipher in config/app.php without migrating existing data first. Your application will throw DecryptException on every attempt to read previously encrypted values.
Session and Authentication Changes
Stricter Session Defaults
Laravel 13 tightens several session defaults that were previously left to the developer:
// Laravel 12 defaults
'lifetime' => 120, // 2 hours
'expire_on_close' => false,
'encrypt' => false,
// Laravel 13 defaults
'lifetime' => 60, // 1 hour
'expire_on_close' => false,
'encrypt' => true, // Session data encrypted at rest
The reduced session lifetime and encryption-by-default are positive security changes. However, if your application relies on longer sessions (admin dashboards, long form workflows), you will need to adjust the lifetime value explicitly.
If you were already encrypting sessions, this change has no effect. If you were not, enabling encryption mid-deployment requires all existing sessions to be invalidated because the framework cannot decrypt unencrypted session data with the encryption driver.
Action item: Plan a maintenance window for the upgrade, or accept that all users will be logged out when session encryption is enabled.
Password Hashing Cost Increases
Laravel 13 increases the default bcrypt cost factor and Argon2 memory parameters:
// Laravel 12
'bcrypt' => ['rounds' => 12],
'argon2id' => [
'memory' => 65536,
'threads' => 1,
'time' => 4,
],
// Laravel 13
'bcrypt' => ['rounds' => 13],
'argon2id' => [
'memory' => 65536,
'threads' => 2,
'time' => 4,
],
Existing password hashes continue to work. Laravel rehashes passwords with the new cost parameters automatically when users log in. No migration is needed, but be aware that login requests will use slightly more CPU with the higher cost factor.
Authentication Guard Changes
Laravel 13 deprecates the once() method on the session guard in favor of a more explicit stateless authentication approach:
// Laravel 12 (deprecated in 13)
Auth::once(['email' => $email, 'password' => $password]);
// Laravel 13 preferred approach
Auth::guard('sanctum')->check();
// Or use explicit stateless middleware
If your API routes use Auth::once() for stateless authentication, migrate to Sanctum token authentication or the new stateless guard.
Middleware Changes
Rate Limiting Updates
Laravel 13 refines the rate limiting middleware with per-route configuration and stricter defaults:
// Laravel 12
Route::middleware('throttle:60,1')->group(function () {
// 60 requests per minute
});
// Laravel 13 adds named limiters with more granular control
Route::middleware('throttle:api')->group(function () {
// Uses the 'api' limiter defined in AppServiceProvider
});
The default API rate limiter is now more restrictive. If your application serves a mobile app or SPA with high request volumes, verify that the new limits accommodate your traffic patterns.
CORS Middleware
Laravel 13 integrates CORS handling directly into the framework's HTTP kernel rather than relying on a separate package. Review your config/cors.php to ensure your allowed origins, methods, and headers are correct:
// config/cors.php - verify these values after upgrading
return [
'paths' => ['api/*'],
'allowed_methods' => ['GET', 'POST', 'PUT', 'DELETE'],
'allowed_origins' => [env('FRONTEND_URL', 'https://yourdomain.com')],
'allowed_headers' => ['Content-Type', 'Authorization', 'X-Requested-With'],
'exposed_headers' => [],
'max_age' => 86400,
'supports_credentials' => true,
];
A common mistake: using 'allowed_origins' => ['*'] in production. This allows any website to make authenticated requests to your API if supports_credentials is also true (browsers will reject this combination, but it indicates a misconfiguration).
Deprecated Security Patterns
Laravel 13 removes or deprecates several patterns. Using them after upgrading may introduce vulnerabilities.
Removed: Legacy Encryption Serialization
Laravel 13 removes support for the legacy serialization format in the encrypter. If you have encrypted data from Laravel 8 or earlier that was never re-encrypted, you must migrate it before upgrading.
Deprecated: Manual CSRF Token Handling
The csrf_token() helper and @csrf directive remain, but manual token verification using Session::token() is deprecated in favor of the framework's middleware-based approach:
// Deprecated in Laravel 13
if ($request->input('_token') !== Session::token()) {
abort(419);
}
// Correct: Let the VerifyCsrfToken middleware handle it
// This is already the standard approach in most applications
Changed: Password Reset Token Hashing
Laravel 13 hashes password reset tokens in the database by default (previously tokens were stored in plain text). This means:
- Existing unhashed tokens in the
password_reset_tokenstable will not work after upgrading - Users with pending password resets will need to request a new link
// No code change needed, but be aware:
// All pending password reset tokens are invalidated on upgrade
Security Headers and Response Changes
Laravel 13 adds a SecurityHeaders middleware that is included in the default middleware stack:
// Automatically added headers in Laravel 13
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
Referrer-Policy: strict-origin-when-cross-origin
If you were adding these headers manually in your web server configuration or through custom middleware, check for duplicates. Duplicate headers can cause unexpected behavior in some browsers.
To customize or disable specific headers:
// bootstrap/app.php
->withMiddleware(function (Middleware $middleware) {
$middleware->configure('security-headers', [
'x-frame-options' => 'DENY', // Override default
'referrer-policy' => 'no-referrer',
]);
})
Upgrade Security Checklist
After upgrading from Laravel 12 to Laravel 13, verify each of these items:
1. Encryption Compatibility
# Test that existing encrypted data can still be decrypted
php artisan tinker
>>> Crypt::decryptString($someExistingEncryptedValue);
If this throws a DecryptException, you need to set the old cipher explicitly or run a data migration.
2. Session Configuration
// Review config/session.php
// Verify lifetime matches your requirements
// Confirm encrypt is set to your preference
// Test that users can log in and maintain sessions
3. Authentication Flows
Test every authentication path:
- Standard login/logout
- "Remember me" functionality
- Password reset flow
- API token authentication
- OAuth/social login (if applicable)
- Two-factor authentication (if applicable)
4. Middleware Stack
# List all registered middleware
php artisan route:list --columns=middleware
Verify that no security middleware was accidentally removed during the upgrade and that new defaults do not conflict with existing middleware.
5. Rate Limiting
# Test your API rate limits
for i in {1..100}; do
curl -s -o /dev/null -w "%{http_code}\n" https://yourapp.com/api/endpoint
done
Confirm that rate limits match your expected thresholds.
6. CORS Configuration
# Test CORS from a different origin
curl -H "Origin: https://other-domain.com" \
-H "Access-Control-Request-Method: POST" \
-X OPTIONS \
https://yourapp.com/api/endpoint -v
7. Deprecated Method Usage
# Search your codebase for deprecated patterns
grep -r "Auth::once" app/
grep -r "Session::token()" app/
grep -r "Crypt::decrypt" app/ # Check for legacy format usage
8. Test Suite
# Run your full test suite
php artisan test
# Pay special attention to authentication and authorization tests
php artisan test --filter=Auth
php artisan test --filter=Security
9. External Verification
After deploying the upgrade, verify your application's external security posture:
- Security headers are present and correct
- Session cookies have proper flags (Secure, HttpOnly, SameSite)
- Debug mode is disabled
- No new information leakage from changed error handling
Planning Your Upgrade
The safest upgrade approach:
-
Read the official upgrade guide in the Laravel documentation. This post covers security-specific changes, but the framework changelog contains additional breaking changes.
-
Upgrade in a staging environment first. Never upgrade production directly.
-
Run your full test suite. If test coverage is low, manually test authentication, authorization, and data encryption flows.
-
Plan for session invalidation. If session encryption is changing, all users will be logged out.
-
Coordinate with your team. Security configuration changes affect everyone. Make sure developers know about new defaults.
-
Monitor after deployment. Watch for authentication failures, decryption errors, and rate limiting issues in your logs for the first 48 hours.
For a complete security review of your Laravel application (not just version-specific changes), see our Laravel security checklist for 2026 and security best practices guide.
You can also review our security checklists for role-specific upgrade verification tasks.
Verify Your Post-Upgrade Security Posture
Configuration changes during upgrades are the most common source of security regressions. A setting that worked in Laravel 12 may behave differently in Laravel 13, and the only way to know for sure is to test from the outside.
After upgrading, run a free StackShield scan to verify that your security posture has not regressed. It checks session cookie flags, security headers, debug mode exposure, and other externally visible security indicators, catching the misconfigurations that slip through during version upgrades.
Is your Laravel app exposed right now?
34% of Laravel apps we scan have at least one critical issue. Most teams don't find out until something breaks. Our free scan checks your live application in under 60 seconds.
Frequently Asked Questions
What are the main security changes in Laravel 13?
Laravel 13 introduces stricter default configurations for encryption, session handling, and middleware behavior. Key changes include the adoption of AES-256-GCM as the default cipher, tighter session cookie defaults, updated rate limiting middleware, refined password hashing cost parameters, and deprecation of several older security patterns. Teams upgrading should carefully review their encryption keys, session configuration, and middleware stack.
Will my Laravel 12 application break when upgrading to Laravel 13?
It depends on which features you use. The most likely breakage comes from removed deprecated methods, changed default configuration values, and updated middleware behavior. Security-specific breaking changes include the new default encryption cipher (existing encrypted data needs migration), stricter password validation rules, and changes to the authentication scaffolding. Always run your test suite after upgrading and review the official upgrade guide.
How do I migrate encrypted data from Laravel 12 to Laravel 13?
If Laravel 13 changes the default cipher, existing encrypted data was created with the old cipher. You have two options: set your config/app.php cipher to the old value explicitly (maintaining backward compatibility), or run a migration that decrypts existing data with the old cipher and re-encrypts it with the new one. Never change the cipher without a migration plan, or you will lose access to encrypted data.
Should I upgrade to Laravel 13 immediately for security?
Not necessarily. Laravel 12 continues to receive security patches during its support window. Upgrade when you have time to test thoroughly, not under pressure. That said, do upgrade before Laravel 12 reaches end-of-life, and adopt the stronger security defaults that Laravel 13 provides. The improved defaults protect against classes of misconfiguration that affect many applications.
Related Articles
Laravel Session Security: Cookies, Hijacking & config/session.php
A deep dive into Laravel session security. Learn how cookie flags, session drivers, and config/session.php settings protect against hijacking, fixation, and sidejacking attacks.
SecurityAutomated Security Testing in Laravel CI/CD Pipelines
How to add security gates to your Laravel CI/CD pipeline with GitHub Actions. Covers dependency scanning, static analysis, secret detection, and automated security monitoring.
SecuritySecuring Laravel Horizon in Production: A Complete Guide
Laravel Horizon exposes your entire queue system, including job payloads, failed jobs with user data, and worker status. Here is how to lock it down properly in production.
Compare StackShield
Security Checklists
Laravel Production Deployment Security Checklist
A comprehensive security checklist for deploying Laravel applications to production. Covers environment config, server hardening, access control, and monitoring.
20 itemsLaravel API Security Checklist
Secure your Laravel API endpoints against common vulnerabilities. Covers authentication, input validation, rate limiting, and response security.
Stay Updated on Laravel Security
Get actionable security tips, vulnerability alerts, and best practices for Laravel apps.