genealabs/laravel-impersonator
GeneaLabs Laravel Impersonator lets trusted admins securely sign in as another user for support and debugging. It offers configurable guards, middleware, Blade helpers, and simple routes to start and stop impersonation without exposing passwords.
1. Match the version line before you install. This package co-versions with Laravel majors: ^13.0 targets Laravel 13 (PHP 8.5 in CI as of 13.0.0), and 10.x is the prior line — there is no 11.x/12.x tag. Check the README support table first, then:
composer require genealabs/laravel-impersonator
2. Publish config and views using the provider tag (don't guess the path):
php artisan vendor:publish --provider="GeneaLabs\LaravelImpersonator\LaravelImpersonatorServiceProvider"
You get config/impersonator.php, which holds the guard allowlist, redirect targets, and takeover behavior. Open this file first whenever behavior looks wrong.
3. Add the impersonation trait to every model that participates — normally App\Models\User, sometimes a separate Admin model. Add CanImpersonate to actors and CanBeImpersonated to targets (or the combined Impersonatable trait if one model plays both roles). The trait exposes impersonate($user) / stopImpersonating() plus canImpersonate() / canBeImpersonated() hooks you will override on day one.
4. Verify session middleware on the routes involved. The swap is written to and read from the session. Without StartSession and the auth guard resolving before your impersonation code runs, identity silently reverts on the next request — this is the number-one "it doesn't work" report.
5. Build the smallest useful loop before anything else:
POST route in your admin panel)POST to stop that restores the original adminGet that round trip green, then add audit logging, approval workflows, and environment gating.
Where to look first when inheriting this integration: config/impersonator.php, the User model's trait overrides, the layout banner partial, and the route middleware group. Those four places are essentially the whole integration.
Debugging checklist when the swap is a no-op: (a) is the route in the web group? (b) does the session driver actually persist (array/cookie won't between requests)? (c) is cached config stale? (d) does the guard in config match the guard your app reads? In tinker, inspect the session keys after a start call — if they aren't there, the middleware chain is wrong, not the package.
Nova action as the primary entry point. This is the package's sweet spot. Expose impersonation from the user resource so support staff never leave the admin UI, and gate visibility with Nova's canSee/authorize so only the right roles see the button. Add a confirmation dialog — impersonation changes who you are, and a mis-click is expensive.
Controller + named route outside Nova. For Blade admin panels, keep it POST-only and policy-gated:
Route::post('/impersonate/{user}', [ImpersonationController::class, 'start'])
->middleware(['auth', 'can:impersonate,user'])
->name('impersonate.start');
Route::post('/impersonate/stop', [ImpersonationController::class, 'stop'])
->middleware('auth')
->name('impersonate.stop');
The package handles the session swap; authorization and redirects are yours. Redirect immediately after calling — don't keep composing a response in the same request (see gotchas).
Centralize both sides of the gate on the model. Don't scatter checks across Nova actions, controllers, and artisan commands:
public function canImpersonate(): bool
{
return $this->hasRole('support')
&& $this->hasConfirmedTwoFactor();
}
public function canBeImpersonated(): bool
{
return ! $this->isAdmin()
&& ! $this->isSystemAccount()
&& $this->isActive();
}
canBeImpersonated() is where you exclude other admins, service accounts, and deactivated users. One rule, every entry point, consistent behavior.
Banner partial in the main layout. Check the impersonation session flag and render a sticky, visually loud bar showing the target's name and avatar plus a "Stop impersonating" form posting to your stop route. The failure mode you're defending against is an admin acting on the wrong account — make it impossible to miss.
Wrap start/stop in a thin internal service with audit logging. The package is deliberately thin and gives you no trail. A small ImpersonationService (or event listeners) should record impersonator_id, target_id, ip, user_agent, optional reason, and timestamps. Log stop as well as start — a start with no stop is an ambiguous session window during incident review.
Test the session, not actingAs(). actingAs() bypasses the impersonation session keys entirely, so tests pass while the feature is broken. In feature tests: authenticate the admin normally, hit the real POST route, assert the identity swapped, then hit the stop route and assert the original admin is restored. Regressions live in the stop path.
Environment guard as a config-level switch. Disable impersonation in production, or restrict it to an allowlist of impersonator emails. A sensible default for an internal tool: on in staging, explicit allowlist in prod, and a config flag you can flip without a deploy.
Compose around the trait for per-tenant behavior. If impersonation must be scoped to a tenant, override the hooks and check tenant membership rather than forking the package. Add a request-scoped "current impersonation" accessor in your own code so views, policies, and logs don't each re-implement the check.
Upgrade workflow. A Laravel major upgrade now implies an impersonation-package bump in the same release train. Put it on the upgrade checklist next to other framework-coupled pins. 13.0.0 is compatibility-only (Laravel 13 + PHP 8.5 in CI) with no documented behavior changes, but the 10 → 13 jump means intermediate releases can't be reasoned about — diff vendor/ if behavior matters to you.
StartSession and after the auth guard resolves. Register it in the web group rather than bolting it onto individual routes; misfiled middleware means the session has nothing to read.auth()->user() is stale within the starting request. After calling impersonate(), the resolved user may still be the old model for the rest of that request. Redirect immediately; don't keep rendering as the new identity.admin and web guards, list the allowed guards in config/impersonator.php. A mismatch writes the swap to a guard nothing reads — a silent no-op that looks like a middleware bug.GET. A GET /impersonate/5 is either blocked or, worse, becomes a CSRF-able identity swap. Always POST, always CSRF-protected, always confirmed.config/impersonator.php, run config:cache in production and config:clear in development. Stale cached config is the most common "the middleware isn't firing" cause and it costs an hour every time.stopImpersonating() can restore the wrong identity — and the bug only surfaces in the second-order case.canImpersonate().array or cookie drivers — common in tests and some API setups — impersonation state vanishes between requests. Verify the driver before filing a bug; it's almost always the session, not the package.vendor/ disappear on the next composer update.^13.0 on Laravel 13). With no ^11/^12 line, verify compatibility on older frameworks yourself rather than trusting a constraint to resolve correctly.How can I help you explore Laravel packages today?