bpocallaghan/impersonate
Laravel package that lets administrators impersonate other users, so you can see the app exactly as they do. Quickly start and stop impersonation sessions, troubleshoot support issues, and verify permissions without needing anyone's credentials.
Install the package and confirm what it actually registers before wiring up any UI:
composer require bpocallaghan/impersonate
php artisan vendor:publish --provider="Bpocallaghan\Impersonate\ImpersonateServiceProvider"
php artisan route:list --name=impersonate
Laravel 5.5+ auto-discovers the service provider. On older versions, add Bpocallaghan\Impersonate\ImpersonateServiceProvider::class to config/app.php.
Look at these three things first:
config/impersonate.php — the user model, auth guard, middleware stack, and redirect targets. These are the only knobs most projects need.route:list --name=impersonate — the package typically registers a "take" route (e.g. impersonate/{user}) and a "leave" route (e.g. impersonate/leave). Copy the exact names into your views.First use case: an admin users table.
{{-- resources/views/admin/users/index.blade.php --}}
@can('impersonate', $user)
@if(auth()->id() !== $user->id)
<a href="{{ route('impersonate', $user) }}" class="btn btn-sm btn-warning">
Login as {{ $user->name }}
</a>
@endif
@endcan
Then surface a persistent banner so nobody forgets they are wearing someone else's account:
{{-- resources/views/layouts/app.blade.php --}}
@if(session()->has('impersonate')) {{-- confirm the exact key in the controller --}}
<div class="alert alert-warning">
You are impersonating <strong>{{ auth()->user()->name }}</strong>.
<a href="{{ route('impersonate.leave') }}">Return to my account</a>
</div>
@endif
That is the whole happy path: install, publish, confirm routes, add a link, add a leave link. Everything else is hardening.
Wrap the package routes in your own authorization middleware. The package supplies the mechanic; it does not know your roles. Point the config at a gate or a dedicated middleware:
// config/impersonate.php
'middleware' => ['web', 'auth', 'can:impersonate-users'],
// app/Providers/AuthServiceProvider.php
Gate::define('impersonate-users', fn (User $user) => $user->hasRole('admin'));
Use the same gate in Blade (@can('impersonate', $user)) so the button and the route agree.
Guard against nested impersonation. If the "take" route is reachable while already impersonating, an admin can end up impersonating from inside an impersonated session and the "leave" route can restore the wrong account. Add a check in your own wrapper route or a small middleware:
if (session()->has('impersonate')) {
abort(403, 'Already impersonating.');
}
Prefer a POST wrapper in production. Most packages of this shape expose a GET route. A GET link that swaps the authenticated user is state-changing and can be triggered by a crafted <img src> if the route is ever reachable without auth. Keep the package route behind auth + role middleware, or register your own POST route that performs the same action and keep the package route out of your public surface.
Programmatic use from an admin panel. If you use Nova, Filament, or a custom admin controller, do not hand-roll the session swap. Redirect to the package route (or call the package controller action) so the session key and redirect behavior stay consistent:
// A Nova action or custom controller
public function handle(Request $request, User $user)
{
abort_unless($request->user()->can('impersonate-users'), 403);
return redirect()->route('impersonate', $user);
}
Audit every impersonation. This is the pattern most teams add after the first incident. If the package dispatches events, listen for them; if it does not, wrap the route:
Route::middleware(['web', 'auth', 'can:impersonate-users'])
->get('admin/impersonate/{user}', function (User $user) {
Log::withContext([
'impersonator_id' => auth()->id(),
'impersonated_id' => $user->id,
]);
Log::info('Impersonation started');
return redirect()->route('impersonate', $user);
});
Carry the impersonator ID into every subsequent log line with Log::withContext() in a middleware, or you will be unable to tell customer actions from admin debugging later.
Testing. Feature-test the full round trip, not just the redirect:
public function test_admin_can_impersonate_and_return(): void
{
$admin = User::factory()->admin()->create();
$customer = User::factory()->create();
$this->actingAs($admin)
->get(route('impersonate', $customer))
->assertRedirect();
$this->assertAuthenticatedAs($customer);
$this->get(route('impersonate.leave'))
->assertRedirect();
$this->assertAuthenticatedAs($admin);
}
Also test the negative case: a non-admin hitting the route must get 403, not a session swap.
Suppress outbound side effects. The point of impersonation is to see the customer's screens, not to email them. While impersonating, consider a middleware that swaps the mailer to the log driver, or gate notification sending on the absence of the impersonation session key. Password resets and order confirmations triggered during debugging are a classic embarrassment.
array driver will not survive between requests, so the round trip silently fails in tests or local setups using it. Use file, database, or redis. In phpunit.xml, set the session driver explicitly.config/impersonate.php, run php artisan config:clear. After adding routes, run php artisan route:clear; a stale route:cache will not include the package's routes and you will get 404s that look like a broken install.admin) but the package is configured for web, then auth()->user() inside the impersonated session resolves to the wrong principal. Set the guard in config and verify with auth()->guard('admin')->user() in a tinker session.lab404/laravel-impersonate. That package uses a fluent API ($user->impersonate($target), $user->leaveImpersonation()) and different middleware. Tutorials and Stack Overflow answers for one will not apply to the other. If both are installed, their session keys and middleware will fight.password.confirm and 2FA middleware can block the impersonated user. If your app requires recent password confirmation for sensitive areas, the impersonated session has not confirmed a password. Expect a prompt or a redirect loop when debugging exactly the screens you wanted to reach.auth()->id() or clear the relevant cache when impersonation starts and ends.@can is only UX.How can I help you explore Laravel packages today?