Laravel gives you a secure starting point. Queries are parameterised, Blade escapes output, forms carry a CSRF token and passwords are hashed. Most Laravel apps that get breached are not breached through the framework. They are breached through a setting that was never changed, a shortcut that was meant to be temporary, or a package nobody updated.
This checklist covers the Laravel security best practices that make the biggest difference once an app is in production. Use it for a new project, or walk through it for every client app you maintain.
1. Get the configuration right
Configuration mistakes are the easiest way in, because they need no exploit at all.
- Turn off debug mode.
APP_DEBUG=falsein production, always. The debug page shows environment variables, queries and file paths to anyone who triggers an error. - Keep the application key secret.
APP_KEYencrypts cookies, sessions and encrypted model attributes. If it leaks, an attacker can forge encrypted data. When you rotate it, put the old key inAPP_PREVIOUS_KEYSso existing encrypted data can still be read. - Point the web server at
public/. Only thepublicdirectory should be reachable. If the project root is the document root, your.envfile may be one request away from the internet. - Secure your cookies. Set
SESSION_SECURE_COOKIE=trueon HTTPS sites and keephttp_onlyenabled, so session cookies are never sent over plain HTTP or read by JavaScript. - Separate environments. Staging and production should never share databases, keys or credentials.
2. Guard against mass assignment
Mass assignment lets a user set attributes you never meant to expose, such as is_admin or team_id.
- Define which attributes are fillable with
$fillable, or the#[Fillable]attribute in recent Laravel versions. Avoid$guarded = []on models that receive user input. - Pass
$request->validated()tocreate()andupdate(), never$request->all(). - Call
Model::shouldBeStrict()in development. It throws an exception when an attribute is silently discarded, which surfaces mass assignment mistakes early.
3. Never build SQL from user input
Eloquent and the query builder use bound parameters, so normal queries are safe. The risk starts where you write raw SQL.
// Vulnerable: user input ends up in the SQL string
Order::whereRaw("coupon = '{$request->code}'")->first();
// Safe: the value is bound as a parameter
Order::whereRaw('coupon = ?', [$request->code])->first();
Watch out for column and direction names too. Bindings only protect values, so a sort parameter passed straight into orderBy() needs an allow list:
$column = in_array($request->sort, ['created_at', 'total'], true) ? $request->sort : 'created_at';
4. Escape output, and know when you don't
Blade's {{ }} escapes HTML. {!! !!} does not. Every {!! !!} in your views should print something you control completely, such as Markdown you rendered with HTML input stripped. User generated content should never pass through it.
The same goes for JavaScript. Use @json or Js::from() to pass data to scripts instead of building JavaScript strings by hand.
5. Authorize every action, not just every page
Authentication tells you who someone is. Authorization tells you what they may do, and it is where most data leaks in multi-tenant apps happen.
- Use policies for every model a user can view or change, and call them from controllers, form requests or Livewire actions.
- Scope queries to the current tenant.
Invoice::find($id)returns an invoice of any customer.$team->invoices()->findOrFail($id)does not. - Test the negative case. A test that proves user B cannot open user A's invoice is worth more than ten tests of the happy path.
6. Validate everything that comes in
Validate all input with form requests or validate(), including input you only use for filtering or sorting. For file uploads:
- Validate the type and size with the
file,mimesandmaxrules. - Store uploads outside the public directory, or on a disk that does not execute code.
- Generate your own file names instead of trusting the name the client sends.
7. Keep CSRF protection and rate limits on
CSRF protection is on by default for web routes. Keep exceptions to a minimum and only exclude routes that authenticate in another way, such as signed webhooks.
Rate limit login, password reset and every endpoint that sends mail or costs money. Laravel's RateLimiter and the throttle middleware make this a one-line change.
8. Update dependencies on a schedule
A Laravel app easily pulls in a hundred packages. Any of them can get a security advisory.
- Run
composer auditin your CI pipeline, so a known vulnerable package fails the build. - Stay on a Laravel version that still receives security fixes, and on a supported PHP version.
- Plan updates. A monthly update routine is far less risky than a big upgrade once a year.
9. Send security headers
Add headers that tell browsers how to treat your site:
Strict-Transport-Securityto force HTTPS.Content-Security-Policyto limit where scripts can come from.X-Content-Type-Options: nosniffandReferrer-Policy.X-Frame-Optionsor theframe-ancestorsCSP directive against clickjacking.
You can set them in your web server or in a small middleware.
10. Protect secrets in your data and logs
- Use the
encryptedcast for sensitive columns, such as API tokens of third party services. - Hide sensitive attributes from serialization with
$hidden. - Make sure passwords and tokens never end up in logs or error reports. Filter them before anything leaves your server.
11. Monitor production
Security is not only prevention. You also need to notice when something goes wrong:
- Track exceptions. A sudden burst of validation or authorization errors on one endpoint often means someone is probing it.
- Watch your certificates and uptime, so an expired certificate or a defaced site does not go unnoticed.
- Scan code and dependencies continuously, not once before launch.
The checklist
| Area | Check |
|---|---|
| Configuration | APP_DEBUG=false, secret APP_KEY, web root on public/, secure cookies |
| Models | Explicit fillable attributes, only validated input, strict mode in development |
| Queries | No user input in raw SQL, allow lists for columns and sort directions |
| Views | {!! !!} only for trusted content |
| Authorization | Policies for every model, queries scoped to the tenant, negative tests |
| Input | Validation everywhere, safe file uploads |
| Requests | CSRF on, rate limits on sensitive endpoints |
| Dependencies | composer audit in CI, supported Laravel and PHP versions |
| Headers | HSTS, CSP, nosniff, frame protection |
| Secrets | Encrypted casts, hidden attributes, filtered logs |
| Monitoring | Errors, uptime, certificates and vulnerabilities |
If you run many client apps, the hard part is not knowing this list. It is applying it to every app, every month. That is what nopanic is built for: errors, uptime, certificates and code security for all your Laravel apps in one place.