Security 11 min read

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.

Matt King
Matt King
May 16, 2026
Last updated: May 16, 2026
Laravel 13 Security: What Changed from Laravel 12 and What You Need to Know

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:

  1. Existing unhashed tokens in the password_reset_tokens table will not work after upgrading
  2. 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:

  1. Read the official upgrade guide in the Laravel documentation. This post covers security-specific changes, but the framework changelog contains additional breaking changes.

  2. Upgrade in a staging environment first. Never upgrade production directly.

  3. Run your full test suite. If test coverage is low, manually test authentication, authorization, and data encryption flows.

  4. Plan for session invalidation. If session encryption is changing, all users will be logged out.

  5. Coordinate with your team. Security configuration changes affect everyone. Make sure developers know about new defaults.

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

Free security check

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.

18% have debug mode on
72% missing security headers
12% have exposed .env
Scan My App Free No signup required. Results in 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.

Stay Updated on Laravel Security

Get actionable security tips, vulnerability alerts, and best practices for Laravel apps.