Skip to content

Scope Login/Logout impersonation clear to the impersonator's guard - #154

Merged
jszobody merged 1 commit into
stechstudio:masterfrom
chengkangzai:fix/guard-scoped-impersonation-clear
May 26, 2026
Merged

jszobody merged 1 commit into
stechstudio:masterfrom
chengkangzai:fix/guard-scoped-impersonation-clear

Conversation

@chengkangzai

Copy link
Copy Markdown
Contributor

Problem

FilamentImpersonateServiceProvider::registeringPackage() clears impersonation on every Login/Logout event, ignoring which guard fired it:

Event::listen(Login::class,  fn () => Impersonation::clear());
Event::listen(Logout::class, fn () => Impersonation::clear());

In apps that share a single session across multiple guards (e.g. an admin guard alongside a separate customer/storefront guard on the same domain), this is unsafe. While an admin is impersonating, an unrelated guard authenticating — a customer logging in on the storefront, or a "remember me" recaller silently re-authenticating on a plain GET — fires a Login/Logout and tears the impersonation down.

The impersonator's own session login key is left untouched, so they remain authenticated as the impersonated user, but isImpersonating() now returns false: the banner disappears and there's no way to leave. The admin is silently stuck as the impersonated user.

Fix

Only end the impersonation when the auth event belongs to a guard involved in the impersonation (the impersonator's guard, or the guard being used):

protected function clearImpersonationForGuard(?string $guard): void
{
    if (Impersonation::isImpersonating()) {
        $impersonationGuards = array_filter([
            Impersonation::getImpersonatorGuardName(),
            Impersonation::getImpersonatorGuardUsingName(),
        ]);

        if ($guard !== null && $impersonationGuards !== [] && ! in_array($guard, $impersonationGuards, true)) {
            return;
        }
    }

    Impersonation::clear();
}

Backward compatibility

  • Single-guard apps: the event guard always matches the impersonator guard, so behaviour is identical.
  • Not impersonating: falls through to Impersonation::clear() exactly as before (e.g. clearing stray keys on a fresh login).
  • Impersonator's own guard logs in/out: still clears — a real login/logout ends the impersonation as expected.
  • Only a foreign guard's auth event is now ignored.

Tests

Adds tests/ImpersonationClearOnAuthEventTest.php covering both paths (unrelated guard preserves; impersonator guard clears; not-impersonating unchanged). Full suite passes locally (135 passed).

The package clears impersonation on every `Login`/`Logout` event regardless
of which guard fired it. In apps that share a single session across multiple
guards (e.g. an admin guard alongside a separate customer/storefront guard),
an unrelated guard authenticating — a customer logging in on the storefront,
or a "remember me" recaller silently re-authenticating on a GET — tears down
an active admin impersonation. The impersonator's own session key is left
intact, so they stay authenticated *as* the impersonated user while
`isImpersonating()` flips to false: no banner, no way to leave.

Only end the impersonation when the auth event belongs to a guard involved in
the impersonation (the impersonator's guard or the one being used). Behaviour
is unchanged for single-guard apps and when not impersonating.

Adds tests covering both the unrelated-guard (preserve) and impersonator-guard
(clear) paths.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@jszobody
jszobody merged commit 2fc8509 into stechstudio:master May 26, 2026
1 check passed
@chengkangzai
chengkangzai deleted the fix/guard-scoped-impersonation-clear branch May 29, 2026 05:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants