arcanedev/laravel-impersonator
Impersonate users in Laravel with a simple, secure API for starting and stopping impersonation sessions. Lightweight package with middleware and Blade helpers for seamless user switching.
Install
composer require arcanedev/laravel-impersonator
The service provider (Arcanedev\LaravelImpersonator\Providers\LaravelImpersonatorServiceProvider) and the Impersonator facade are auto-discovered on Laravel 5.5+. Then publish the config so you can see what knobs exist before writing any code:
php artisan vendor:publish --provider="Arcanedev\LaravelImpersonator\Providers\LaravelImpersonatorServiceProvider"
Where to look first
config/laravel-impersonator.php — this is the real API surface. It controls the user model class, the route prefix/middleware for the built-in take/leave endpoints, and the session key used to stash the impersonator's original ID.src/Impersonator.php — the facade root. Expect take($user), a "leave"/"stop" method, and status checks like isActive()/isImpersonating(). The README is thin, so read the class.src/Traits/ — an Impersonatable (or similarly named) trait you add to your User model so it can answer "is this user being impersonated?"src/Http/ — the built-in controller/middleware behind the packaged routes.First use case
The canonical one: a support/admin user clicks "Login as this customer" from your admin panel, sees the app exactly as the customer does, and a persistent banner offers "Return to my account". Minimal wiring:
// User model
use Arcanedev\LaravelImpersonator\Traits\Impersonatable;
class User extends Authenticatable
{
use Impersonatable;
}
// Admin controller
public function impersonate(User $user)
{
abort_unless(auth()->user()->can('impersonate'), 403);
Impersonator::take($user);
return redirect()->route('dashboard');
}
Keep the config's user_model pointing at the same class you put the trait on, otherwise the guard swap will silently fail or throw a ModelNotFoundException.
Authorization belongs outside the package. The package only swaps the guard; it does not decide who may impersonate. Put the policy check in your own controller/middleware and keep the packaged routes behind auth + your admin gate:
Route::middleware(['auth', 'can:impersonate'])->group(function () {
Route::get('impersonate/{user}', [ImpersonationController::class, 'take'])->name('impersonate.take');
Route::get('impersonate/leave', [ImpersonationController::class, 'leave'])->name('impersonate.leave');
});
Banner pattern. Every layout should render a persistent strip while impersonation is active, with the "leave" link. Blade:
@if (Impersonator::isActive())
<div class="alert alert-warning">
Impersonating <strong>{{ auth()->user()->name }}</strong>.
<a href="{{ route('impersonate.leave') }}">Return to my account</a>
</div>
@endif
If the package ships @impersonating / @endImpersonating directives, prefer them only after checking src/Providers — the directive list is easy to verify and easy to be wrong about.
Wrap sensitive actions. A ProtectFromImpersonation middleware (or your own) on routes like password change, billing, account deletion, and API-token creation is the single most valuable integration:
// app/Http/Middleware/BlockWhileImpersonating.php
public function handle($request, Closure $next)
{
abort_if(Impersonator::isActive(), 403, 'Not available while impersonating.');
return $next($request);
}
Apply it by group, not route-by-route — new routes then inherit the protection by default.
Audit logging. Fire-and-forget impersonation is a compliance problem. Log both directions, keyed to the original user:
Log::info('impersonation.start', [
'actor' => auth()->id(), // the impersonator
'target' => $user->id,
'ip' => request()->ip(),
]);
Wrap this in a small service or an event listener so both the controller and the leave action call the same code.
Testing pattern. In feature tests, avoid hitting the impersonation HTTP routes unless you're testing them; use the facade or session key directly and assert on behavior:
public function test_impersonated_user_cannot_change_email(): void
{
$admin = User::factory()->admin()->create();
$user = User::factory()->create();
$this->actingAs($admin);
Impersonator::take($user);
$this->put('/settings/email', ['email' => '[email protected]'])
->assertForbidden(); // guarded by your middleware
}
Then a second test that asserts auth()->id() === $admin->id after "leave" — that round-trip (target → back to original) is where impersonation implementations break most often.
Route model binding + signed URLs. If you use the packaged take route, pass the user via route model binding and consider adding Laravel's signed middleware to the URL so links can't be shared/screenshotted into a privilege escalation.
web session middleware (API routes, queued jobs dispatched during impersonation, artisan commands) will not see the impersonated user. Jobs should be dispatched with an explicit user ID, not rely on auth().SESSION_DRIVER=array, state survives within a single test request chain but not across simulated separate requests. If assertions fail mysterically in tests, switch to database/file for the test suite or assert on the facade in-process.take() either overwrites or is rejected. Guard it: abort_if(Impersonator::isActive(), 403);Auth::user() is correct but a stale remember_token, a password.confirm timeout, or a cached Auth::user() in a service provider can disagree. If you hit this, re-resolve the guard (Auth::forgetGuards() in older Laravel) after leave.laravel/framework or fork it. With only 7 stars and 0 dependents there is effectively no maintenance, so budget for vendoring (vcs repository entry or a path repo) rather than assuming upstream fixes. If the only thing you need is session()->put('impersonator', $id) plus a guard swap, ~40 lines of your own code is a legitimate alternative and removes the dependency risk entirely.abort_if(auth()->id() === $user->id, 422);POST, the banner link needs a form or @method/@csrf. If you make it GET, note that a prefetch/scan can silently end an impersonation session — harmless, but confusing to the admin who clicked away.user_model and route settings mean you can repoint the package at a tenant-scoped model or an admin guard without touching vendor code. That's usually the right lever when the package doesn't do what you need — change config, add your own controller, and only call take()/leave from it.How can I help you explore Laravel packages today?