For: anyone running Laravel in production. No attack steps here — this is a working reference for hardening: a priority-ordered checklist, the dangerous defaults, per-area guidance, and self-verification. For the cross-framework picture, see the security-by-framework hub.
Priority-ordered hardening checklist
Do this table top-down. P0 is a prerequisite for the rest, P1 is the most frequent source of incidents, P2 is ongoing maintenance.
P0 ── Prerequisite (do first)
Debug off in prod / secrets off the public surface at perms 600 / manage APP_KEY
P1 ── Top source of incidents
Authorization (Policy/Gate) / Mass Assignment control / dependency CVEs / prod caching
P2 ── Ongoing maintenance
Sessions/CSRF/cookies / upload validation / HTTPS, headers, rate limiting
| Priority | Control | Specifics (Laravel) |
|---|---|---|
| P0 | Disable production debug | APP_DEBUG=false / APP_ENV=production, pinned with config:cache. Don't leak internals on the error page |
| P0 | Secrets out of the public surface | .env, backups, keys outside public/, perms 600. storage/logs not public |
| P0 | Manage APP_KEY safely | Underpins encryption, signed cookies, sessions. Inject from env; rotate on leak |
| P1 | Explicit authorization | Policy / Gate + authorize() / can middleware, scoped by owner/permission |
| P1 | Control Mass Assignment | Declare $fillable; avoid bulk-assigning $request->all(), use validated() |
| P1 | Monitor Composer CVEs | composer audit / osv-scanner; judge by running version, patch fast |
| P1 | Production caching | config:cache route:cache view:cache for reliable settings + speed |
| P2 | Session/cookie safety | Set secure http_only same_site; regenerate the session on login |
| P2 | Upload validation | Validate type/size, store outside public/, no execute permission |
| P2 | HTTPS/headers/rate limiting | Force HTTPS, HSTS, throttle on login/API |
1. Secrets and APP_KEY (P0)
Laravel keeps .env outside the document root (at the project root) by default. Incidents mostly come from putting secrets where they don't belong.
- Never place
.env, DB dumps, backups, or key files inpublic/. Keep secrets outside the app root at perms 600 (owner-only). - Don't expose
storage/orstorage/logs/(logs can contain secrets or personal data). Don't commit.envto the repo. APP_KEYunderpins encryption, signed URLs, encrypted cookies, and sessions. Inject it from the environment and rotate promptly if leaked (note that this invalidates existing encrypted data/sessions).
For the general principle see keep secrets out of public directories; for a real full-exposure case see a full .env exposure.
2. Production config: DEBUG, environment, caching (P0)
APP_DEBUG=false / APP_ENV=production
Pin config with caching
php artisan config:cache (+ route:cache view:cache) makes settings apply reliably and speeds the app. Note: after config:cache, env() outside config/ returns null, so reference values via config('...') in the app.Don't expose diagnostic tools in production
3. Authorization: Broken Access Control (P1 — the top source)
The most common production incident is "authenticated but not authorized." Being able to log in doesn't mean being allowed to do the action.
Common (dangerous)
- no authorization — "logged in = can view/update"
- route-model binding fetches another user's ID
Model::create($request->all())accepts every field- a privilege field like
is_adminassigned from user input
Correct
- Policy / Gate +
authorize()/canmiddleware, scoping owner/permission every time - reads too are owner-scoped (e.g.
where('user_id', $me)) - limit input with
$request->validated()(FormRequest) - declare
$fillableto block Mass Assignment
See what IDOR is. The key is an ownership check on every read/update/delete path.
4. Injection and output
Laravel's Eloquent/query builder binds values with placeholders, and Blade escapes output by default. The rule is don't disable that safety yourself.
- SQL: don't mix user input into
whereRaw/DB::rawby string concatenation. Even when raw SQL is needed, use bindings (→ what SQL injection is). - XSS: Blade
{{ }}escapes;{!! !!}is raw output. Don't pass user input to{!! !!}. If you must emit HTML, only after sanitizing (→ what XSS is). - Validation: validate type/range/allowed values with FormRequest /
validate()before use.
5. Sessions, CSRF, cookies (P2)
- CSRF: don't globally disable the web middleware's CSRF protection. Use
@csrfin forms and proper token handling for SPAs (→ what CSRF is). - Cookies/sessions: set
secure(HTTPS),http_only,same_siteinconfig/session.php. Regenerate the session on login to prevent fixation (Laravel's auth regenerates). - Brute force: apply
throttle/ login-attempt limits to login.
6. File uploads and public files (P2)
- Validate mime type, extension, and size, and don't trust the client-supplied filename.
- Store outside
public/(e.g.storage/) with no execute permission. Serve only what needs to be public, via a controlled path. - Put authorization on delivery too, so sequential direct links can't fetch another user's file.
7. HTTPS, security headers, rate limiting (P2)
- Force HTTPS (
URL::forceScheme('https'), etc.) + HSTS. Behind a load balancer, useTrustProxiesso the scheme is detected correctly. - Security headers (X-Content-Type-Options, etc.; design CSP around your asset setup). Check your own site with the security headers checker.
- Rate limiting: apply
throttleto login, APIs, and password reset.
8. Dependencies and versions (P1)
- Monitor Composer dependency CVEs with
composer auditor osv-scanner, judge by the running version, and patch fast (→ monitoring dependency CVEs · the vulnerability-response playbook). - Keep Laravel and PHP on supported versions. Don't leave EOL versions in place (unfixed vulnerabilities pile up).
Verify: is your Laravel actually hardened?
Building it isn't the end — it's done only once you've checked. These are defensive self-checks against your own site/environment.
Secrets aren't fetchable by URL
/.env and /storage/logs/laravel.log and confirm they return 404 (if fetchable, fix immediately and rotate keys).No debug exposure in production
Authorization holds
Cookies/sessions and dependencies
composer audit is clean.This site's view: even with strong defaults, authorization and dependencies are your responsibility
Laravel has many good defaults, but authorization — who may do what — and keeping dependencies up to date are app- and operation-specific, so no framework can guard them automatically. The incidents we keep seeing are less elaborate attacks than settings/operations patterns: "authenticated but no ownership check," "debug open in production," "a secret exposed." So the focus is working through the table above from the top and doing the basics consistently: scope every read/update by owner, and monitor dependencies for CVEs.
Read next
- Hub: security by framework · Next.js security
- Secrets: keep secrets out of public directories · Case: a full .env exposure
- Authorization / practice: what IDOR is · the vulnerability-response playbook · monitoring dependency CVEs
- Glossary: SQL injection · XSS · CSRF
Sources and scope
These primary references support the settings, scope or remediation discussed above; links were checked on October 23, 2026. Consult their version and configuration conditions. Checklist priorities and lessons are this site's guidance, not a full-article revalidation or a safety guarantee.