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

Laravel Impersonate Laravel Package

lab404/laravel-impersonate

Laravel Impersonate lets administrators log in as any user for support and debugging. It offers Blade directives, middleware, and guard helpers to safely start and stop impersonation without passwords, supporting multiple guards while keeping the original session recoverable.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and rely on package discovery (Laravel 5.5+); there is nothing to register manually:

composer require lab404/laravel-impersonate
php artisan vendor:publish --provider="Lab404\Impersonate\ImpersonateServiceProvider" --tag=config

Publish the config early — it gives you session_key (default impersonated_by), take_redirect_to, leave_redirect_to, and the default guard. Then add the trait to your authenticatable model and lock it down immediately, because both policy methods return true by default:

// app/Models/User.php
use Lab404\Impersonate\Models\Impersonate;

class User extends Authenticatable
{
    use Impersonate;

    public function canImpersonate(): bool
    {
        return $this->hasRole('support') || $this->hasRole('admin');
    }

    public function canBeImpersonated(): bool
    {
        return ! $this->hasAnyRole(['admin', 'billing_owner']) && ! $this->mfa_enrolled;
    }
}

The first real use case is a support console button. Two POST routes — take and leave — plus a persistent banner, is the whole happy path:

Route::middleware('auth')->group(function () {
    Route::post('/impersonate/{user}', [ImpersonationController::class, 'take'])->name('impersonate.take');
    Route::post('/impersonate/leave', [ImpersonationController::class, 'leave'])->name('impersonate.leave');
});
public function take(Request $request, User $user)
{
    abort_unless($request->user()->canImpersonate(), 403);
    abort_unless($user->canBeImpersonated(), 403);
    abort_if($request->user()->is($user), 403);

    $request->user()->impersonate($user);

    return redirect()->route('dashboard');
}

public function leave(Request $request)
{
    $request->user()->leaveImpersonate();

    return redirect()->route('admin.users.index');
}

After that, the two things you'll reach for daily are is_impersonating() / Impersonate::isImpersonating() and Impersonate::getImpersonator().

Implementation Patterns

Guard the route, not just the model. canImpersonate() is a model method — pair it with a gate or middleware on the route so a misconfigured model can't expose the endpoint:

Route::post('/impersonate/{user}', ...)->middleware('can:impersonate-users');

Always ship a banner. The package deliberately provides no UI. A layout-level block is the cheapest safety net you'll build:

@impersonating
    <div class="impersonation-banner">
        Impersonating <strong>{{ auth()->user()->name }}</strong>.
        <form method="POST" action="{{ route('impersonate.leave') }}">
            @csrf
            <button>Leave</button>
        </form>
    </div>
@endImpersonating

Directives available: @impersonating / @endImpersonating, @canImpersonate, @canBeImpersonated (each accepts an optional guard name), plus the helpers is_impersonating(), can_impersonate(), can_be_impersonated($user).

Log via events, not controllers. Lab404\Impersonate\Events\TakeImpersonation and LeaveImpersonation both carry $impersonator and $impersonated as Authenticatable instances. Put the audit write in a listener so it fires no matter which entry point triggers impersonation:

Event::listen(TakeImpersonation::class, function (TakeImpersonation $e) {
    ImpersonationLog::create([
        'impersonator_id' => $e->impersonator->getAuthIdentifier(),
        'impersonated_id' => $e->impersonated->getAuthIdentifier(),
        'ip'              => request()->ip(),
        'created_at'      => now(),
    ]);
});

Block sensitive routes with the shipped middleware. impersonate.protect (aliased by the service provider to Lab404\Impersonate\Middleware\ProtectAgainstImpersonation) aborts when impersonating. Apply it to password change, email change, MFA enrolment, billing, and account deletion — never globally:

Route::middleware('impersonate.protect')->group(function () {
    Route::get('/settings/security', ...);
    Route::post('/billing/payment-method', ...);
});

Multi-guard is the differentiator — use it deliberately. Pass the guard explicitly and keep both models trait-equipped:

auth('admin')->user()->impersonate($customer, 'web');

Impersonate::getImpersonator();          // resolved back through the original guard
@impersonating('web') ... @endImpersonating

Note the guard-name handling in getImpersonator() and in "can't leave" was fixed only in 1.7.2 — pin at least that.

Feature-test the switch. Asserting on the session key and the authenticated user catches almost every wiring mistake:

$this->actingAs($support)->post(route('impersonate.take', $customer))->assertRedirect();

$this->assertAuthenticatedAs($customer);
$this->assertTrue(session()->has(config('impersonate.session_key')));

Gotchas and Tips

  • Default-open policy. canImpersonate() and canBeImpersonated() return true out of the box. Overriding them is step one, not a later hardening task.
  • Attribution is your job. Every action taken while impersonating is written as the impersonated user's action. The package emits events; it does not stamp your audit trail. Add a middleware that pushes impersonator_id into your log context on every request while is_impersonating() is true.
  • Session-only. Impersonation swaps the user inside the existing session guard. Token/stateless APIs, Sanctum personal-access-token flows, and Auth::onceUsingId()-style auth won't participate. If your app is API-first, this is the wrong tool.
  • Route verb and CSRF. Take/leave must be POST with CSRF. A GET impersonation endpoint will eventually be triggered by a browser prefetch, a link scanner, or a support agent's back button.
  • No built-in expiry. Once taken, impersonation persists until leaveImpersonate() or session expiry — an abandoned tab stays impersonated for hours. Consider a timestamp in the session checked by middleware, or a short session lifetime for support sessions.
  • password.confirm leakage. Laravel's recent-password-confirmation flag lives in the session, so an impersonated session can inherit the admin's confirmation window. Keep impersonate.protect on anything behind password.confirm.
  • Queued work captures the wrong identity. Jobs and notifications dispatched mid-impersonation serialize the impersonated user. Dispatch audit/mail with explicit user IDs rather than reading auth()->user() later.
  • Config redirects are easy to bypass. If you write your own controller and call redirect() yourself, take_redirect_to / leave_redirect_to never apply. 1.7.6 added dynamic redirection on leave — check which path you're actually on before debugging a redirect that "doesn't respect config."
  • MissingUserProvider / InvalidUserProvider (1.7.0+) mean the guard in config/auth.php has no provider or a non-Eloquent one. It fails loudly instead of silently — good, but it will surface at impersonation time, not boot time, so smoke-test each guard in staging.
  • Non-integer keys are fine now. Identity resolution uses getAuthIdentifier(), so UUID/ULID models work — but only past 1.5.1/1.7.0. Older versions assumed id, including in can_be_impersonated().
  • Shared session domains. If several apps sit on the same cookie domain, change session_key to something app-specific or you'll get cross-app impersonation reads.
  • Extension seam. Custom policy belongs in the model's canImpersonate / canBeImpersonated overrides and in event listeners around the trait — the package is intentionally a primitive, not a governance layer. Don't fork it to add approval workflows; wrap it.
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.
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin