Back to Blog
4 min read

Securing Forced Password Resets in Laravel 12: Handling Auth State Across Impersonation Sessions

The Problem: Security Gaps After Legacy Migration

When we migrated AustinsElite from a legacy system to Laravel 12, one non-negotiable was security hygiene: every user had to reset their password on first login. This wasn’t just about compliance—it was about ensuring no stale, potentially compromised credentials lingered post-migration.

We implemented a simple flag in the user table: must_reset_password. If true, the app would intercept any authenticated request and redirect to a dedicated password update page. Simple enough—until admin impersonation entered the picture.

Admins use Laravel Filament’s impersonation feature to debug user issues. But when an admin impersonates a user who needs to reset their password, the expected redirect chain breaks. The backend knows the user is flagged, but the frontend (a Next.js app) doesn’t respect that state consistently. Result? Users slipped through the cracks, landing in the main app with outdated passwords.

This wasn’t just a UX hiccup—it was a security hole.

The Break: When Impersonation Skews Auth State

Here’s where things got messy.

During impersonation, the session is technically tied to the admin’s original identity, but the authenticated user context is swapped. Our initial logic relied on Laravel’s Auth::user() to check the must_reset_password flag and return a JSON response telling the Next.js frontend to redirect. But because the session wasn’t being flagged correctly in the context of impersonation, that signal never fired.

Worse, after the password reset, the user could be redirected back into an impersonation session—but now with updated credentials, while the admin remained logged in. This created a confusing hybrid state: the frontend thought the user was fully authenticated, but backend session logic was still tied to the impersonator.

We had two systems making assumptions about auth state, and neither was talking clearly to the other.

The breaking point came when a QA test revealed that a user flagged for password reset was able to access protected routes during impersonation—bypassing the reset entirely. That triggered an immediate fix.

The Fix: Syncing State Across Laravel, Filament, and Next.js

Our solution had three layers: backend enforcement, session signaling, and frontend coordination.

First, we moved the redirect logic from the frontend into Laravel middleware. Instead of relying on the Next.js app to interpret a JSON flag, we made the redirect authoritative at the HTTP level. We created a EnsurePasswordIsUpdated middleware that runs on all authenticated routes:

if ($user->must_reset_password && !\request()->is('force-reset')) {
    return redirect()->route('password.force-reset');
}

But here’s the key: we preserved impersonation context by checking Filament::auth()->check() and using \request()->session()->get('_impersonator_id') to detect if we’re in an impersonated session. If so, we still enforce the redirect—but we preserve the impersonation flag so the user doesn’t lose context after resetting.

Next, we updated the password reset endpoint to accept and return impersonation state. After a successful reset, instead of sending the user to /dashboard, we check:

  • Is _impersonator_id in the session?
  • If yes, redirect back to Filament’s impersonation exit route, which safely resumes the admin’s session.
  • If no, proceed to the standard user dashboard.

This ensured that admins weren’t accidentally logged out—and users didn’t get stuck in limbo.

Finally, on the Next.js side, we updated our API layer to handle 302 redirects gracefully. Since Laravel now issues real redirects (not JSON hints), we had to ensure our fetch calls didn’t swallow them. We added a simple check:

if (response.redirected) {
  window.location.href = response.url;
}

This let the browser handle the redirect naturally, preserving session integrity.

The result? A seamless, secure flow: user logs in → gets redirected to reset → resets password → returns to correct context (either as themselves or back to the admin’s view).

Lessons Learned

This wasn’t just about fixing a redirect. It was a reminder that authentication state is fragile when multiple systems are involved—especially when you add impersonation, middleware layers, and decoupled frontends.

The fix seems small in hindsight, but it required deep coordination between Laravel’s session handling, Filament’s impersonation logic, and Next.js’s client-side routing. The real win was shifting responsibility to the backend for security-critical decisions, while letting the frontend react appropriately.

If you’re building a Laravel + Next.js stack with admin tools, don’t assume auth state is consistent during impersonation. Test edge cases hard. And when security is involved, let the server have the final word.

Newer post

Building a Reusable Avatar Upload System in Filament PHP with Security First

Older post

How We Unified a Fragmented Data Import System in Next.js with a Single Command