Secure a Laravel Application: OWASP Risks and Practical Defences
Laravel gives you secure primitives; it cannot decide who may access an invoice, which URL a server may fetch, or what a browser is allowed to load. Security is a set of explicit boundaries, tested at the same level as business behaviour.
Authorize on the server
Use policies for model actions and gates for capabilities. The controller invokes authorization before it reads or mutates data.
<?php
declare(strict_types=1);
namespace App\Policies;
use App\Models\User;
final class UserPolicy
{
public function viewAny(User $user): bool
{
return $user->hasPermissionTo('users.view');
}
}
For a super-admin escape hatch, put the rule in one reviewable place:
Gate::before(fn (User $user): ?bool => $user->isSuperAdmin() ? true : null);
On Laravel 11+, controller middleware is declared with HasMiddleware; constructor $this->middleware() is not the current pattern.
final class AdminUserController extends Controller implements HasMiddleware
{
public static function middleware(): array
{
return ['auth', 'can:viewAny,'.User::class];
}
}
Client-side hiding is UX, never authorization. Feature-test a denied user for every sensitive route.
Validate, bind and encode
Form Requests are the HTTP boundary. Use $request->validated(), not $request->only(), so unvalidated fields cannot slip into mass assignment. Eloquent and the query builder parameterize values; never concatenate input into SQL, shell commands or dynamic column names. Escape output by default and sanitize deliberately if user HTML is a product feature.
Credentials and sessions
Passwords use Laravel’s hashed cast or Hash::make()—hashes are not recoverable. API token systems should store token secrets hashed at rest; show the plaintext token only once. Encrypt reversible integration credentials with an encrypted cast, protect APP_KEY, and rotate secrets with an overlap plan.
Use Sanctum for first-party SPAs and personal access tokens when its model fits. Give tokens narrow abilities, revoke them on compromise, rate-limit login and reset endpoints, and enable email verification/MFA where the risk warrants it. Never log Authorization headers, cookies or reset URLs.
Browser and deployment boundaries
Serve only public/, set APP_DEBUG=false, use HTTPS and secure cookie settings. Add security headers deliberately: a Content-Security-Policy tailored to actual scripts, X-Content-Type-Options: nosniff, clickjacking protection, a strict referrer policy and HSTS after HTTPS is stable. Start CSP in report-only mode, inspect violations, then enforce it; copying a permissive policy defeats its purpose.
Audit Composer and npm dependencies in CI, pin intentional upgrades, and remove unused packages. composer audit reports known advisories; it is not a complete application security review.
SSRF, uploads and external input
An endpoint that fetches a user-provided URL can reach internal infrastructure. Parse and allow-list intended hosts, resolve DNS defensively, reject loopback, link-local and private address ranges, disable redirect surprises, set short timeouts and restrict egress at the network layer. A hostname regex alone is not protection.
Validate upload MIME type and size, store uploads outside the public root, generate server-side names and scan high-risk files. Signed URLs are useful for time-limited access, not a substitute for checking authorization.
Observe and rehearse
Log failed authentication and authorization with minimal identifiers; report unexpected exceptions to your error tracker without secrets or personal payloads. Queue listeners and webhooks must be idempotent. Security work is complete only when a test proves the bad path is rejected and an operator can investigate it safely.
Production checklist
- Policies/Gates cover every sensitive action and are feature-tested.
- Requests use validated data; database and shell input is never concatenated.
- Tokens are scoped and hashed; secrets are encrypted or environment-owned.
- HTTPS, debug settings, public web root and headers are reviewed per environment.
- SSRF, upload and webhook boundaries have explicit tests and operational limits.