
8 minutes read
19 Laravel security best practices for 2025
- → Introduction
- → Don’t track your .env file
- → Enforce HTTPS and HSTS
- → Keep Laravel up to date
- → Keep your first and third-party packages up to date
- → Disable debug messages in production
- → Don’t send sensitive information to error monitoring tools
- → Restrict parts of your app with policies
- → Rate limit your endpoints
- → Protect your forms from cross-site request forgery (CSRF)
- → Validate the user’s input
- → Be careful with uploaded files
- → Guard against mass-assignment
- → Output escaping, XSS & CSP
- → Secure cookie flags
- → Encrypt the payload of your jobs
- → Multi-factor authentication & passkeys
- → Write tests for security risks
- → Do regular security audits
- → Bonus: Security.txt & responsible disclosure
Introduction
Security is a broad topic and this article doesn’t cover it fully. Is this even possible anyway?
That being said, I want to give you as much actionable best practices, tips, and tricks to help you consolidate your apps.
All tips are relevant for Laravel 10, 11, and 12. If you’re not sure whether your Laravel version is still getting security fixes, check Laravel’s support policy before you keep reading.
Don’t track your .env file
Your .env file contains sensitive information.
Please, don’t track it! Make sure it’s included in your .gitignore and not accidentally included in production images or deployments (Docker, anyone?). Use Docker secrets or AWS Parameter Store for production secrets.
Most of the time, data leaks are inside jobs. A password manager is a better solution for sharing credentials.
If you want your team members to have access to a curated set of sensitive information, use a password manager with a proven track record of rock-solid security.
Enforce HTTPS and HSTS
No excuse for HTTP in 2025. In AppServiceProvider
:
use Illuminate\Support\Facades\URL; public function boot() { if ($this->app->environment('production')) { URL::forceScheme('https'); } }
Set the Strict-Transport-Security
header in your web server or reverse proxy to ensure browsers never downgrade.
Keep Laravel up to date
Keeping Laravel up to date allows you to stay in touch with the latest security updates.
Here’s what you need to know as of July 2025:
- Laravel 12 is current (bug fixes until Aug 2026, security fixes until Feb 2027)
- Laravel 11 is still in its security-fix window
- If you’re on an older version, you’re on your own
If you never upgraded a Laravel project, I wrote a guide that will teach you how to do it. (Yes, upgrading can be a pain, but not upgrading is Russian roulette.)
Keep your first and third-party packages up to date
Access to dozens of packages from the official Laravel ecosystem and thousands of community packages is what makes our job easier.
But the more packages you use, the more points of failure you can be subject to.
Regularly running composer update
goes a long way toward a more secure codebase. But don’t stop there—add composer audit
to your routine. This will catch vulnerable dependencies before someone else does:
composer audit --format=table
Automate this with Dependabot or Renovate, and only merge if tests + audit pass.
Disable debug messages in production
Make sure these two environment variables are correctly set in production.
APP_ENV=production APP_DEBUG=false LOG_LEVEL=error
You don’t want to leak information about your app’s architecture or configuration. Usually, debug messages contain a lot of this kind of details.
Remember, APP_DEBUG=false
disables stack traces in browser, but your logs might still leak too much info. Crank LOG_LEVEL
down to error
or critical
in production.
Don’t send sensitive information to error monitoring tools
Talking about sensitive information in debug messages, we haven’t eradicated them yet.
If you are using an error monitoring tool, like Flare, you have to hide them there as well.
PHP 8.2 introduced a new attribute, \SensitiveParameter
, that can hide anything from the stack trace (which is sent to error monitoring tools).
function something( #[\SensitiveParameter] $top_secret_parameter, $who_cares_parameter, ) { throw new \Exception('Whoops, something bad happened!'); }
Restrict parts of your app with policies
Policies in Laravel are like nightclub bouncers preventing people from accessing restricted areas.
Here’s a real-world example of using a Policy:
// app/Policies/PostPolicy.php public function update(User $user, Post $post) { return $user->id === $post->user_id; } // app/Http/Controllers/PostController.php public function update(Request $request, Post $post) { $this->authorize('update', $post); // ... }
Use policies for every non-trivial access check. It’s easy to get lazy—don’t.
Rate limit your endpoints
Brute force and credential-stuffing attacks are everywhere. Add some throttle to your critical routes. Example for logins:
use Illuminate\Cache\RateLimiting\Limit; use Illuminate\Support\Facades\RateLimiter; RateLimiter::for('login', function ($request) { return Limit::perMinute(10)->by($request->ip()); });
Protect your forms from cross-site request forgery (CSRF)
You might want to use the @csrf
Blade directive in your forms.
<form method="POST" action="{{ route('register') }}"> @csrf <p> <label for="name">First Name</label> <input type="text" id="name" name="name" value="{{ old('name') }}" required /> </p> … </form>
This directive generates a hidden input field containing a CSRF token automatically included when submitting the form.
This token confirms that the form is being submitted from your application and not by a third party.
The verification is handled by the VerifyCsrfToken middleware that Laravel uses by default for all your web routes.
Learn more about CSRF protection in Laravel’s documentation.
Validate the user’s input
Validation in Laravel is crucial in ensuring your application’s security.
Validation rules are numerous and will help you sanitize the data your users send with ease. Because you know the drill, right? Never trust your users’ input.
use Illuminate\Http\Request; class PostController extends Controller { function store(Request $request) { $validated = $request->validate([ 'user_id' => 'required|exists:users,id', 'title' => 'required|string|min:3|max:255', 'content' => 'required|string|min:3', 'published' => 'sometimes|boolean' ]); Post::create($validated); // } }
In Laravel 12+, secureValidate()
gives you stricter, safer defaults.
Learn more about validation in Laravel on the official documentation.
Be careful with uploaded files
As we saw, the user’s input must never be trusted. That also goes for the files they upload. Here are a few recommendations:
- Check the file’s MIME type (Laravel has the right validation rules for that).
$request->validate([ 'file' => 'required|mimes:gif,jpeg,png,webp', ]);
- When possible, don’t make uploaded files publicly accessible (use the
local
file driver, for instance). - Upload files on another server. If a hacker bypasses your securities, they won’t be able to run unauthorized code and access sensitive information.
- Delegate file uploads to a third-party service reputed for its security (meaning they never leaked data).
Guard against mass-assignment
If you use Eloquent, make sure you’re protecting yourself from mass-assignment attacks. Only allow fields you explicitly trust:
// In your Eloquent model protected $fillable = ['user_id', 'title', 'content', 'published']; // Or, better, use form request classes to control what goes in.
Output escaping, XSS & CSP
Cross-site scripting is still everywhere. By default, Blade’s {{ }}
escapes output. Only use {!! !!}
when you know what you’re doing.
Want extra peace of mind? Use spatie/laravel-csp to set a strong Content Security Policy. Sanitize any user HTML with Purifier::clean()
or similar.
Secure cookie flags
Set your cookies right in config/session.php
:
'secure' => true, 'http_only' => true, 'same_site' => 'lax',
This helps block session hijacking and CSRF.
Encrypt the payload of your jobs
Whenever you dispatch a job, its payload is saved into the database, Redis or whatever else you told Laravel to use using the QUEUE_DRIVER
environment variable.
The payload may contain sensitive information that any of your employee may look at and potentially misuse. As I said in the beginning of this article, leaks are often initiated by employees.
Since Laravel 11, you can enforce global queue encryption by adding this to your AppServiceProvider
:
use Illuminate\Support\Facades\Queue; public function boot() { Queue::encrypt(); }
Or implement the Illuminate\Contracts\Queue\ShouldBeEncrypted
Contract for specific jobs:
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldBeEncrypted; class SomeJob implements ShouldQueue, ShouldBeEncrypted { // }
To decrypt, the APP_KEY
in your .env must be secure and restricted.
Multi-factor authentication & passkeys
Passwords aren’t enough. For APIs, use Laravel Sanctum or Passport with WebAuthn/passkeys. For web apps, Fortify or Jetstream can set up MFA in no time. In 2025, anything less is reckless.
Write tests for security risks
Testing is unfortunately a vast and lesser-known topic among developers.
Automatically testing multiple scenarios for potential security breaches is a great way to make sure they stay closed.
Laracasts provides free testing courses to help you get started. One with PHPUnit, the industry standard, and one with Pest, the best testing framework on this planet that modernizes and simplifies testing in PHP.
Add static analysis (larastan
or psalm
) to catch issues early.
Do regular security audits
This practice is one of the most efficient and should be mandatory for anyone that is really serious about security. External feedback can be eye-opening.
As you can imagine, doing security audits isn’t free. It might only be worth it for enterprise since it would cost even more to pay for the potential fines! You cannot put a price on maintaining a good reputation and the trust of your users.
Bonus: Security.txt & responsible disclosure
Want to look professional and make life easier for white-hats? Add a /.well-known/security.txt
file pointing to a security contact or bug bounty policy. It’s a tiny effort that can prevent a lot of headaches.
Did you like this article? Then, keep learning:
- Fix common Laravel errors related to caching and environment for better security
- Ensure proper error handling to prevent leakage of sensitive application details
- Keep Laravel and your packages up-to-date to stay protected against vulnerabilities
- Master Laravel authorization with policies for robust app access control
- Secure your REST APIs using Laravel Sanctum quickly and efficiently
- Control user permissions with Laravel policies to protect sensitive operations
- Improve Laravel app security by writing tests focused on common risks
- Learn to effectively validate user input, a key Laravel security step
- Stay current with PHP versions and security implications relevant to Laravel
- Understand defensive programming practices to reduce risks without extensive tests
0 comments