pushery/fortify-hardening-for-laravel
Security hardening for Laravel Fortify. Adds stricter password rules, login throttling and rate limiting, safer session defaults, and optional two-factor enforcement, so your app's authentication is secure out of the box with minimal configuration and no loss of Fortify features.
Architecture fit. The package now covers two Fortify hardening surfaces, not one. v0.2.0 addressed login rate-limit key privacy; v0.3.0 extends the registration-enumeration side: a notice is sent to the holder of an already-registered address, the notice names the application in its subject (app.name), a configurable subclass can override applicationName() and greeting() for multi-instance installs, and a new fortify-hardening.registration.notice_mentions_password_reset flag stops the notice from suggesting a password reset in passwordless applications. The test helper assertEnumerationSafe() now supports Livewire forms by fingerprinting snapshots without checksums, normalizing generated component IDs to <component-id>, and replacing echoed identities with <identity> across status, errors, and redirects. Fit is therefore broader: Fortify apps with public registration, passwordless/magic-link sign-in, Livewire-based registration forms, and multi-tenant installations all gain relevant capability. Fit remains low for apps without Fortify or with fully custom registration and throttling. The package is still pre-1.0 and bound to Fortify internals, so treat it as security-sensitive infrastructure, not a convenience library.
Integration feasibility. Likely a composer update plus config and translation publishing. Verify that the new config keys behave correctly under config cache, that the notification subclass named in fortify-hardening.registration.notification is autoloadable and constructed with the expected contract, and that applicationName() / greeting() receive the notifiable as documented. Confirm whether the notice is sent synchronously in the registration request path or queued; synchronous mail changes latency and failure behavior. The Livewire-aware assertion likely requires Livewire test utilities and may require updating existing tests that compared raw JSON byte for byte. Feasible for a Laravel team already on Fortify, but the test-helper change is a CI-visible behavioral change, not just a fix.
Technical risk. New risks center on outbound notification: sending mail to the holder of a taken address can be abused for mail bombing, drives deliverability and support load, and exposes app.name (or a per-instance name) to external recipients — a branding and information-disclosure consideration for multi-tenant installs. Translation coverage must be validated across the seven supported languages, including the :app placeholder. The subclass contract (applicationName(), greeting(), notifiable argument) is an extension point that may shift before 1.0. The Livewire fingerprint normalization must be strict enough that a genuine differential field still fails the assertion — the release claims this, but it should be verified with a deliberate known-vs-unknown difference, not only with the passing case. Defaults for notice_mentions_password_reset and the notification class must preserve v0.2.0 behavior for existing adopters. Existing risks remain: Fortify internal binding, mixed-version rolling deploy splitting rate-limit counters, APP_KEY rotation or node mismatch, stale config cache, and historical raw email/IP in cache and error-tracker telemetry.
Key questions. Which Laravel, Fortify, PHP, and Livewire versions are supported? Does the Livewire-aware assertEnumerationSafe() require Livewire as a dev dependency, and does it still fail on a real differential field? What are the defaults for registration.notification and registration.notice_mentions_password_reset, and do they preserve prior behavior? Is the notice queued, rate-limited, and safe against repeated triggering for the same address? Does applicationName() fall back safely when unset, and what is exposed in the subject for multi-tenant installs? Are all seven translations published and tested with :app? Is the notification subclass contract documented as stable? Are old raw-key entries and error-tracker records still purged or redacted? What is the package's support and security policy?
Stack fit. Best fit: Laravel apps on Fortify with shared cache (Redis, database, memcached), standard email/password or passwordless login, public registration, and optionally Livewire-driven forms. Poor fit: apps without Fortify, custom registration/throttling, or externally delegated auth. TPM should scope v0.3.0 as a security/privacy hardening release plus a registration-notification feature, not a user-facing auth change.
Migration path. From v0.2.0: update the package, republish or diff config and translations, explicitly set registration.notification and registration.notice_mentions_password_reset per environment, clear config cache, and run registration and login tests. Existing adopters should confirm default behavior matches v0.2.0 before changing flags. Passwordless apps should set notice_mentions_password_reset to false. Multi-tenant installs should ship a notification subclass that returns the correct instance name. Rollback remains the env flag for the rate-limit path; the notification behavior rolls back via config, but reverting code restores raw-key storage only if the flag is set false, so treat that as incident-only.
Compatibility. Validate composer constraints against the app's Fortify, Laravel, PHP, and Livewire versions. Test with the production cache store, cache prefix, config cache, mail transport, queue, and error tracker. Confirm the notification subclass is discoverable under config cache. Confirm translations render correctly in every enabled locale. Verify the updated test helper against the app's existing enumeration tests and adjust assertions where raw-body comparison was assumed. Confirm APP_KEY is stable and identical across nodes for the rate-limit path.
Sequencing. 1) Inventory Fortify version, registration routes/guards, Livewire usage, mail transport/queue, cache driver, and error-tracker cache instrumentation. 2) Stage with production-like cache, mail, and tracker; verify no raw email/IP appears in new cache entries or tracker payloads. 3) Configure notification naming and passwordless copy; verify subject, greeting, and translations. 4) Add mail-volume safeguards and confirm the notice is queued or otherwise bounded. 5) Update and run assertEnumerationSafe() tests, including a deliberate differential-field case. 6) Verify Fortify's 5/min limit, wait, and response codes are unchanged. 7) Deploy atomically or use a two-phase enable to avoid mixed old/new limiter behavior, accepting a brief counter-reset window. 8) Clear config cache, monitor 429s, login failures, mail volume, bounces, and support tickets. 9) Purge/redact historical PII per retention policy.
Maintenance. Still low code maintenance, but now two security-sensitive surfaces plus translations and a notification extension point. Track Fortify and Livewire releases, re-test the limiter binding on Fortify majors, and re-test the Livewire fingerprint on Livewire majors. Keep the env flag and notification config documented, with the env flag treated as incident-only. Translation updates add a recurring, if small, maintenance task.
Support. The runbook expands: recipients will ask why they received a "someone tried to register" notice; support needs a script explaining the enumeration-safe behavior and how to report abuse. Multi-tenant naming errors are a visible brand issue. Passwordless apps that miss the flag will send misleading reset guidance. Existing runbook items remain: deploy resets failed-login counts, APP_KEY rotation resets or splits counters, enabling/disabling the flag changes cache-key semantics, and historical telemetry may still contain PII.
Scaling. Hash overhead remains negligible. The new scaling factor is outbound notification volume: an attacker can trigger notices for any known address, so mail volume, cost, and sender reputation must be bounded by queueing and per-address or per-IP throttling. Rate-limit consistency still depends on a shared cache and a stable APP_KEY. No database migration or extra storage is introduced by the rate-limit path; confirm whether notification sending adds queue or logging storage.
Failure modes. Mail outage or synchronous send slows or blocks registration; notification storms harm sender reputation and support capacity; app.name or an unset subclass leaks the wrong instance name; mistranslated or missing :app copy produces broken subjects; the Livewire-aware assertion becomes too permissive and hides real enumeration differences; config cache staleness leaves old copy or old limiter behavior; mixed-version rollout splits counters; APP_KEY mismatch or rotation causes inconsistent limits; a Fortify or Livewire upgrade breaks the binding or the fingerprint; the env flag left false re-leaks raw email/IP; historical telemetry retains PII. Monitor for an unexpected drop in 429s after deploy and for spikes in notification volume.
Ramp-up. Low-to-moderate for a Laravel team familiar with Fortify, Livewire, and cache. Allocate a small hardening effort covering dependency review, notification and translation configuration, staging verification with real mail and cache, CI test updates for the Livewire assertion, deploy coordination, telemetry cleanup, and runbook updates. Acceptance criteria: Fortify's 5/min limit, wait, and responses are unchanged; no new raw email/IP appears in cache or error-tracker payloads; the notice names the correct application, greets the holder, respects the passwordless setting, and is correctly translated; notification volume is bounded; assertEnumerationSafe() passes for an identical-response Livewire form and fails for a deliberate differential field; behavior is stable across multiple nodes; rollback is documented; and historical PII is handled per policy.
How can I help you explore Laravel packages today?