Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Impersonate Laravel Package

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.

View on GitHub
Deep Wiki
Context7

Getting Started

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:

  1. config/impersonate.php — the user model, auth guard, middleware stack, and redirect targets. These are the only knobs most projects need.
  2. The output of 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.
  3. The package controller — it is small. Read how it stores the original user ID in the session, because that session key is what you will check in Blade and in middleware.

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.

Implementation Patterns

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.

Gotchas and Tips

  • Session driver matters. Impersonation is session-based. The 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 and route caching. After publishing or editing 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.
  • Guard mismatch is the most common bug. If your app uses a non-default guard for admins (e.g. 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.
  • The leave route is a lifeline; protect it. If session lifetime is short or the session is regenerated by a login event, the stored original user ID can be lost and you are stranded as the customer. Test the "session expires mid-impersonation" path deliberately; the safe fallback is to log out rather than stay authenticated as the customer.
  • Do not confuse this with 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.
  • Route model binding and soft deletes. The route typically binds the configured user model. Soft-deleted customers may 404 or, worse, be impersonatable if the binding does not exclude them. Decide explicitly.
  • 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.
  • Caches keyed by user leak between accounts. Menus, permission lists, and dashboard widgets cached under a generic key will show the admin's data under the customer's identity. Key caches by auth()->id() or clear the relevant cache when impersonation starts and ends.
  • Hiding the button is not authorization. The route exists regardless of whether you render the link. The middleware/gate is the security boundary; the Blade @can is only UX.
  • The package is small and low-traffic (single-digit stars). Read the controller and config directly rather than relying on blog posts; the surface area is small enough to audit in ten minutes, and the exact route names, session key, and config keys are the things most likely to differ from a tutorial. Note them in your project README so the next developer does not have to rediscover them.
  • Extension point: if you need custom behavior (logging, redirect destinations, blocking certain users), publish and override the config first; only subclass or replace the controller if the config genuinely cannot express it. Overriding internals couples you to a package with a small maintainer base.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel
draftsmaninc/draftsman
square-bit/laravel-volition
himito/laravel-mailman
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log