Join our Newsletter — 33% off our NHI Course
Home› Guides› Laravel Security Guide: Mass Assignment, Eloquent Queries, APP_KEY…
Guide Application Security

Laravel Security Guide: Mass Assignment, Eloquent Queries, APP_KEY and Signed URLs

PDF for newsletter subscribers · free to read online

← All guides
By NHI Mgmt Group, edited by Lalit Choda Updated 27 October 2026 15 min read
On this page

Laravel ships with secure defaults, and most serious Laravel incidents come from teams switching one of them off: debug mode left on, an unguarded model, a raw query with user input, or an APP_KEY that leaked with a .env file. The application key is particularly sensitive because it encrypts cookies and sessions and signs URLs, and CISA lists a Laravel deserialisation flaw that becomes remote code execution once an attacker holds that key. This guide covers how authentication, authorisation and API tokens work in Laravel, how to control privileged tooling, how to manage the keys and integrations a Laravel application depends on, what to log, and a hardening baseline covering mass assignment, Eloquent and query builder safety, encryption and signed URLs. It is written for PHP developers, application security engineers and platform teams running Laravel in production, and reflects the Laravel 13.x documentation as read on 27 October 2026.

Key takeaways

  • Treat APP_KEY as a master secret. It encrypts every cookie and session and signs URLs. If it leaks, rotate it with APP_PREVIOUS_KEYS and assume session forgery is possible on older or unpatched code paths.
  • Mass assignment protection only works if you keep it. Eloquent rejects mass assignment until you declare fillable or guarded attributes; Unguarded models and $request->all() passed to create() reopen the hole.
  • Parameter binding stops value injection, not identifier injection. PDO cannot bind column names, so any user-controlled column, sort key or validation rule column needs an allowlist.
  • Signed URLs sign the query string, nothing else. Read signed values with $request->query(), because request body input takes precedence and is not covered by the signature.
  • API tokens never expire unless you say so. Sanctum tokens are hashed at rest but have no default expiry, so set expiration, give tokens narrow abilities and prune expired ones.

How identity and access work in Laravel

Laravel separates authentication into guards (how a request is authenticated, for example session or token) and providers (where users come from, usually Eloquent). Authorisation is handled by gates and policies. Most applications combine several mechanisms, and each has its own security properties.

MechanismUsed forSecurity properties to know
Session guard (web)Browser usersSession cookie encrypted with APP_KEY; request forgery protection from the PreventRequestForgery middleware in the web group
Sanctum SPA authenticationFirst-party single page appsUses cookie sessions, not tokens; SPA and API must share a top-level domain; CSRF protection applies
Sanctum API tokensMobile apps, scripts, simple third-party accessStored as SHA-256 hashes; abilities act like OAuth scopes; no expiry by default
PassportFull OAuth 2.0 serverUse when third parties need delegated access with standard grants and scopes
Gates and policiesAuthorisation decisions per action and modelGate::before and a policy before() method run first and can grant everything

Request forgery protection in Laravel 13

Laravel 13 formalised request forgery protection as PreventRequestForgery. It first checks the browser's Sec-Fetch-Site header and falls back to the CSRF token if origin verification does not pass. The documentation notes that browsers send Sec-Fetch-Site only over HTTPS, so plain HTTP sites rely entirely on the token. preventRequestForgery(originOnly: true) removes the token fallback (failures return 403 instead of 419), and allowSameSite: true accepts subdomain requests. Exclude webhook routes by placing them outside the web group rather than by growing an exclusion list.

Authorisation patterns that hold up

  • Authorise every action on the server: use $this->authorize(), Gate::authorize() or can middleware on routes, not just hidden buttons in Blade templates. Missing object-level checks are the most common Laravel API flaw. See the OWASP API Top 10 API1 Broken Object Level Authorization Guide.
  • Combine token abilities with policies: Sanctum's tokenCan always returns true for first-party SPA requests authenticated by session, so the documentation advises that policies still decide whether the user may act.
  • Use route model binding with scoping so nested resources are resolved within their parent, not by global ID.

Admin roles and privileged access

Privilege in a Laravel estate sits in three places: application roles, operational dashboards and the command line.

Privileged surfaceRiskControl
Gate::before super-admin shortcutOne role bypasses every policy, including future onesKeep the callback narrow, return null rather than false for non-admins, log its use, and require MFA for that role
Admin panels (Nova, Filament or custom)Full data access, often with export featuresSeparate route group with its own middleware, MFA, IP restriction where practical, and audit logging of exports
Telescope, Horizon, Pulse and similar dashboardsShow requests, queries, jobs, exceptions and sometimes payloadsGate access to named operators, disable or restrict in production, and never expose them without authentication
Artisan and Tinker on serversTinker is a REPL with full application and database accessRestrict shell access to deployment identities and named engineers through privileged access tooling; record sessions
Maintenance mode bypassphp artisan down --secret=... issues a bypass cookie to anyone who visits the secret URLUse --with-secret to generate it, share it privately and bring the site back up promptly
Debug mode and error pagesThe documentation warns APP_DEBUG=true in production risks exposing sensitive configuration valuesFail deployment if APP_DEBUG is true outside local environments

Ignition, the debug error page package, shows why debug tooling matters: CVE-2021-3129, an Ignition file handling flaw that let unauthenticated attackers run code, is in the CISA Known Exploited Vulnerabilities catalogue and is marked as used in ransomware campaigns. Development tools should never be reachable in production.

Integrations, API tokens and service accounts

A Laravel application is also a holder of non-human credentials: its own key, database and queue credentials, mail and payment API keys, cloud storage keys and tokens it issues to others.

CredentialWhere it livesRecommended handling
APP_KEY.env, read through config/app.phpGenerate with php artisan key:generate; unique per environment; stored in a secrets manager or the platform's secret store; rotate with APP_PREVIOUS_KEYS
Database, Redis, queue credentials.envPer-environment accounts with least privilege; the web application rarely needs schema change rights
Third-party API keys (mail, payments, storage).env and config/services.phpScoped keys, IP restrictions where offered, rotation owner recorded
Sanctum personal access tokenspersonal_access_tokens table, hashedNarrow abilities, expiration set, per-token expiry for integrations, sanctum:prune-expired scheduled
Values your app stores for users (for example a user's cloud API token)DatabaseEncrypt with Crypt::encryptString or an encrypted cast; remember they then depend on APP_KEY
Composer and VCS credentials for private packagesauth.json, CI secretsRead-only, never committed, separate from deployment credentials
  • Keep .env out of source control: the documentation says unencrypted environment files should never be committed. If you must version configuration, php artisan env:encrypt produces .env.encrypted; the decryption key is shown once and belongs in a password manager or secrets store, not in the same repository.
  • Serve only public/: the deployment documentation warns that serving the project root exposes sensitive configuration files. A misrouted web root is how .env files, and the keys inside them, end up on the internet. The 230 Million AWS Cloud Environments Campaign harvested keys from exposed .env files at scale.
  • Issue tokens per integration: one Sanctum token per client with only the abilities it needs, for example orders:read, enforced with the abilities (all listed) or ability (any listed) middleware.
  • Treat Composer dependencies as code that runs on every request: in the Laravel-Lang Composer Compromise, attackers rewrote release tags so that a malicious src/helpers.php ran as soon as vendor/autoload.php loaded. Commit composer.lock, review dependency changes and rotate secrets on any host that installed affected versions.

See the Secrets Management Guide for vault patterns and the API Key Management Guide for issuing keys to customers.

Logging and detection

EventHow to capture it in LaravelWhy it matters
Failed logins, lockouts, password resetsAuthentication events (Failed, Lockout, PasswordReset) with listeners writing to a dedicated log channelCredential stuffing and account takeover
Authorisation denialsLog in a policy after hook or exception handler for AuthorizationExceptionObject-level probing of APIs
Invalid signed URL attemptsHandle InvalidSignatureException and log the routeTampering with download, unsubscribe or invitation links
Token creation, use from new IPs, revocationSanctum token events or last_used_at monitoringStolen API tokens
Requests for /.env, /storage/logs/, /_ignition/, /telescopeWeb server or WAF logsScanners hunting keys and debug tooling
Admin actions and data exportsAudit trail model or package, written outside the main application database where possibleInsider misuse and post-compromise exfiltration

Use Log::withContext() to add a request ID and user ID to every entry so events can be correlated. Never log request bodies wholesale: passwords, tokens and personal data end up in storage/logs, which is itself a common exposure when the web root is wrong. Map detections to T1190 Exploit Public-Facing Application and T1552.001 Credentials In Files.

Metrics

  • Debug-safe deployments: share of production deployments where a pipeline check confirmed APP_DEBUG=false and no debug packages are installed. Target 100%.
  • Unguarded models: number of models using Unguarded or an empty guarded list, target zero outside seeders.
  • Raw query findings: open static analysis findings for DB::raw, whereRaw and orderByRaw with request input.
  • Token hygiene: share of active Sanctum tokens with an expiry and abilities narrower than *.
  • Supported framework version: share of applications on a Laravel version still receiving security fixes.

Hardening baseline

Mass assignment

Eloquent refuses create() and fill() until the model declares fillable or guarded attributes, because, as the documentation puts it, a user could otherwise send a field such as is_admin. In Laravel 13 this is declared with PHP attributes such as #[Fillable(['name'])]; earlier versions use the $fillable and $guarded properties.

  • Prefer fillable allowlists to guarded denylists, because new columns are then protected by default. For JSON columns, each nested key must be listed (for example options->enabled); Laravel does not support updating nested JSON attributes through a guarded model.
  • Pass validated data, not raw input: Model::create($request->validated()) or a Form Request, never $request->all().
  • Avoid Unguarded on any model reachable from a controller. If you use it, the documentation says to hand-craft every array passed to fill, create and update.
  • Surface mistakes in development: Model::preventSilentlyDiscardingAttributes($this->app->isLocal()) throws instead of silently dropping unfillable fields.

See the OWASP API Top 10 API3 Broken Object Property Level Authorization Guide for the API form of this flaw.

Eloquent and query builder safety

PatternSafe?Why
User::where('email', $request->input('email'))YesValue is bound through PDO
User::where($request->input('col'), 'x') or orderBy($request->input('sortBy'))NoPDO cannot bind column names; validate with an allowlist such as 'sortBy' => 'in:price,updated_at'
whereRaw('price > ?', [$min])Yes, if every value uses bindingsRaw expressions are injected as strings; Laravel cannot guarantee their safety
DB::raw("... $input ...")NoString interpolation into SQL
Rule::unique('users')->ignore($id, $request->input('col'))NoOWASP notes validation rules that take column names are injectable the same way

See the SQL Injection Prevention Guide.

Encryption and the application key

  • What the key protects: Laravel's encrypter uses OpenSSL with AES-256 or AES-128, and Crypt::encryptString uses AES-256-CBC with a MAC so tampered values fail to decrypt. Every cookie, including the session cookie, is encrypted, and the OWASP Laravel Cheat Sheet notes the key also protects signed URLs and password reset tokens.
  • Rotation without logging everyone out: set the new APP_KEY and move the old one to APP_PREVIOUS_KEYS. Laravel encrypts with the current key and tries previous keys on decryption. Re-encrypt stored data, then remove the old key.
  • Why a leaked key is serious: CISA's KEV entry for CVE-2018-15133 describes a Laravel deserialisation flaw allowing remote command execution that "may only be exploited if a malicious user has accessed the application encryption key". Patched versions remove that route, but a leaked key still allows forged cookies and decryption of anything encrypted with it. Rotate on any suspicion.
  • Passwords use hashing, not encryption: the Hash facade defaults to bcrypt (12 rounds) and supports Argon2i and Argon2id.

Signed URLs

  • Use URL::temporarySignedRoute() with a short expiry for downloads, invitations and unsubscribe links, and protect the route with the signed middleware (returns 403 on failure).
  • Read signed values with $request->query('email'). The documentation warns that the signature covers only the path and query string, that the request body takes precedence when input is merged, and that even a GET request can carry a JSON body.
  • Use hasValidSignatureWhileIgnoring() only for harmless parameters such as page number: ignored parameters can be changed by anyone.
  • A signed URL is a bearer credential. Do not log full URLs, and pair sensitive actions with an authenticated session.

Configuration baseline

SettingProduction value
APP_DEBUGfalse
APP_ENVproduction
Web rootpublic/ only; deny /.env, /storage and dotfiles at the web server
config/session.phpsecure true on HTTPS-only sites, http_only true, same_site lax or strict, domain null unless subdomains need it, short lifetime for high-value apps
config/sanctum.phpexpiration set; sanctum:prune-expired scheduled
File permissionsOWASP suggests directories at most 775 and files at most 664; only storage and bootstrap/cache writable by the web server
Cachesphp artisan optimize at deploy so configuration is cached and .env is not read per request
Framework versionA release still receiving security fixes: Laravel 12 until 24 February 2027, Laravel 13 until 17 March 2028

Rolling it out

  1. Find the gaps: grep for Unguarded, $guarded = [], $request->all(), DB::raw, whereRaw, orderByRaw and hasValidSignatureWhileIgnoring, and scan deployed hosts for public .env and debug routes.
  2. Fix configuration first: debug off, web root, session settings and token expiry are low-risk changes; deploy them to staging and run the test suite.
  3. Rotate exposed keys: if .env was ever public or committed, rotate APP_KEY with APP_PREVIOUS_KEYS and every credential in that file.
  4. Fix code patterns behind feature flags where behaviour changes, such as moving from $request->all() to validated data.
  5. Enforce in CI: static analysis rules for the patterns above, a deployment check on APP_DEBUG, and dependency audit with composer audit. Roll back by redeploying the previous release; keep previous keys in APP_PREVIOUS_KEYS until rollback is no longer possible.

Decision guidance: choose Sanctum SPA authentication for your own front end on the same domain, Sanctum tokens for first-party mobile apps and simple integrations, and Passport when third parties need standard OAuth flows. Small teams should lean on framework defaults and a managed platform's secret store; larger estates should standardise a base package that enforces the baseline across applications.

Common misconfigurations

  • Debug mode in production: exposes configuration and stack traces, and debug packages such as Ignition have been exploited directly (CVE-2021-3129).
  • Project root served instead of public/: makes .env, logs and source readable, handing over APP_KEY and every credential.
  • Shared APP_KEY across environments or copied from a tutorial: a key from staging or a public repository unlocks production cookies.
  • $request->all() into create() on a guarded model: new sensitive columns become writable the day they are added.
  • Sort and filter parameters passed straight to orderBy or where: identifier injection that bindings do not stop.
  • Non-expiring * ability tokens: every leaked integration token becomes a permanent full-access credential.
  • Livewire and other component packages left unpatched: CISA added CVE-2025-54068, a Livewire code injection flaw, to KEV in March 2026.

Framework mapping

FrameworkReferenceLaravel control
OWASP ASVS 5.0V15.3.3 mass assignment; V1.2.4 parameterised queries; V1.5.2 safe deserialisation; V13.4.2 debug modes disabledFillable allowlists, bindings and allowlisted identifiers, patched framework and protected key, APP_DEBUG=false
NIST SP 800-53 Rev. 5SC-12, SC-28APP_KEY management and encryption of stored secrets
NIST SP 800-53 Rev. 5AC-3, AC-6Policies on every action, narrow admin shortcuts
NIST SP 800-53 Rev. 5SI-10, SI-2, CM-7Validation, patching to supported versions, no debug tooling in production
NIST CSF 2.0PR.DS-01, PR.PS-01Data at rest protection, configuration management
MITRE ATT&CK v20T1190, T1552.001, T1195.002Exploitation of the app, harvesting .env credentials, malicious Composer packages

Practitioner checklist

  • Confirm APP_DEBUG=false in every production environment and remove debug packages from production installs.
  • Serve only public/ and block /.env, /storage and dotfiles at the web server.
  • Generate a unique APP_KEY per environment, store it in a secrets manager and document rotation with APP_PREVIOUS_KEYS.
  • Use fillable allowlists and validated input for every mass assignment; ban Unguarded on request-facing models.
  • Allowlist every user-influenced column, sort key and validation rule column; review all raw expressions.
  • Read signed URL values with $request->query() and use short-lived temporary signed routes.
  • Set Sanctum token expiry, issue narrow abilities per integration and schedule sanctum:prune-expired.
  • Gate admin panels, Telescope, Horizon and similar dashboards to named operators with MFA.
  • Log authentication, authorisation, signature and token events with request context to a central store.
  • Stay on a Laravel release that receives security fixes and run composer audit in CI.

Standards and references

Related NHI Mgmt Group resources: PHP and WordPress Security Guide · Secrets Management Guide · SQL Injection Prevention Guide · API Security Guide

PDF for subscribers

Take this guide with you

Every section, table and checklist in one PDF, with links to the sources and related guides. Subscribe to our weekly NHI & AI Identity Journal and we’ll email you a download link.

Free weekly newsletter

Subscribe to the NHI & AI Identity Journal

The latest on NHI and Agentic AI security – articles, research, breaches, news and events every week.

Bonus 33% off our NHI Course when you subscribe.

NHIMG Editorial Note
Researched and written by the NHI Mgmt Group editorial team using AI tools, under the editorial direction of Lalit Choda. Facts are drawn from the sources listed on this page and checked by our automated quality process. Last updated 27 October 2026. Spotted an error? Use our contact page.
NHI Mgmt Group, the independent authority on Non-Human Identity, IAM and Agentic AI security. nhimg.org

PDF Downloads for Newsletter Subscribers

Get the guide

Enter your details and we’ll email you a download link.

The full guide is free to read online. The PDF is a thank you for subscribing.

Fill in all fields marked * and tick the PDF box to continue.