Currently Available: Need a skilled Software Developer for your next project?
Categories
Laravel

Implementing User Impersonation in Laravel + Inertia + Vue Using laravel-impersonate

Implementing user impersonation in a modern Laravel stack with Inertia, Vue, and Sanctum you need to maintain proper authentication state in the SPA context. Simply switching users isn't enough, as you'll need to handle Sanctum's session management to prevent logouts.

This guide shows how to implement user impersonation for applications using:

  • Laravel 11 as the backend framework
  • Inertia.js + Vue for the frontend
  • optinally Laravel Jetstream
  • Sanctum for SPA authentication

We'll leverage the lab404/laravel-impersonate package to handle the core impersonation logic.

1. Setting Up laravel-impersonate

Install the package via Composer and publish its configuration:

composer require lab404/laravel-impersonate

php artisan vendor:publish --tag=impersonate

This creates a config/laravel-impersonate.php file where you can customize the package's behavior. Most modern Laravel applications use package auto-discovery, so you won't need to manually register the service provider.

Next, add the Impersonate trait to your User model:

use Lab404\Impersonate\Models\Impersonate;

class User extends Authenticatable
{
    use Impersonate;

    // ... your existing model code
}

This trait provides each User instance with two key methods:

  • impersonate($otherUser): Start impersonating another user
  • leaveImpersonation(): Return to your original user account

2. Setting Up the Routes

You have two approaches for handling impersonation routes:

Option A: Using the Built-in Route Macro

Add this single line to your routes/web.php:

Route::impersonate();

This automatically registers two RESTful routes:

  • POST impersonate/{id} - Starts impersonation of a specified user
  • POST impersonate/leave - Ends the current impersonation session

In your Vue components, you can link to these routes using Inertia's Link component:

<!-- Start impersonation -->
<Link
  :href="route('impersonate', user.id)"
  method="post"
  as="button"
  class="btn btn-primary"
>
  Impersonate User
</Link>

<!-- End impersonation -->
<Link
  :href="route('impersonate.leave')"
  method="post"
  as="button"
  class="btn btn-warning"
>
  Return to My Account
</Link>

This will fire the package's TakeImpersonation and LeaveImpersonation events, which we'll use in the next step.

3. Maintaining Sanctum Authentication During Impersonation

Here's where things get interesting. When using Sanctum in an SPA, simply impersonating another user isn't enough – you need to update Sanctum's session data to maintain the authentication state. The key is to listen for the impersonation events and update the necessary session variables.

Add this to your AppServiceProvider's boot method:

use Lab404\Impersonate\Events\TakeImpersonation;
use Lab404\Impersonate\Events\LeaveImpersonation;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Auth;

public function boot(): void
{
    // When impersonation begins
    Event::listen(function (TakeImpersonation $event) {
        session()->put([
            'password_hash_sanctum' => $event->impersonated->getAuthPassword(),
        ]);
    });

    // When impersonation ends
    Event::listen(function (LeaveImpersonation $event) {
        session()->forget('password_hash_web');
        session()->put([
            'password_hash_sanctum' => $event->impersonator->getAuthPassword(),
        ]);

        // Ensure proper user restoration
        Auth::setUser($event->impersonator);
    });
}

This code ensures your Sanctum authentication persists smoothly through the impersonation process, preventing unexpected logouts in your SPA.

4. Adding a Visual Impersonation Indicator

It's good practice to provide clear visual feedback when an administrator is impersonating another user. Add this component to your main layout:

<template>
  <div
    v-if="$page.props.impersonatedBy"
    class="bg-red-500 text-white p-4 flex items-center justify-between"
  >
    <span class="font-medium">
      You are currently viewing the application as another user
    </span>
    <form :action="route('impersonate.leave')" method="post" class="inline-block">
      <button type="submit" class="underline font-semibold hover:text-red-100">
        Return to Your Account
      </button>
    </form>
  </div>
</template>

This banner provides a persistent reminder and a quick way to end the impersonation session.

5. Implementing Impersonation Security (Frontend)

To prevent unauthorized impersonation, implement these methods in your User model:

public function canImpersonate(): bool
{
    // Example: Only allow administrators to impersonate
    return $this->hasRole('administrator');
}

public function canBeImpersonated(): bool
{
    // Example: Prevent impersonation of superadmins
    return !$this->hasRole('superadmin');
}

You can customize these methods based on your application's permission structure, whether you're using roles, policies, or another authorization approach.

6. Securing Impersonation with Superadmin Middleware (Backend)

You need to make sure that only admins (I call them superadmin) can access the impersonation routes. You can do this by wrapping the built-in route macro with your custom superadmin middleware in routes/web.php. For example:

// routes/web.php

Route::middleware(['auth:sanctum', 'verified', 'superadmin'])->group(function () {
    Route::impersonate();
});

This way, even if someone tries to guess or manually visit the impersonation URL, they’ll be blocked unless they’re actually a superadmin (or currently impersonated by one). This extra check prevents unauthorized users from initiating or ending impersonation sessions.

Here's what my superadmin middleware looks like:

// app/Http/Middleware/EnsureSuperAdmin.php

class EnsureSuperAdmin
{
    /**
     * Handle an incoming request.
     *
     * @param  \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response)  $next
     */
    public function handle(Request $request, Closure $next): Response
    {
        // If you're not already superadmin and you're not impersonated by a superadmin, block access
        if (!$request->user()->is_superadmin && ! session('impersonated_by')) {
            abort(403, 'This action is unauthorized.');
        }

        return $next($request);
    }
}

To make it work with the routes file as shown above you need to give it an alias:

// bootstrap/app.php

$middleware->alias([
            // ...
            'superadmin' => \App\Http\Middleware\EnsureSuperAdmin::class,
        ]);
What I'm building

Delegate tasks. Get software.

Give Vroni a GitHub issue, bug report, spec, or rough idea. It reads the repo, plans the change, writes code, runs checks, and works toward a review-ready pull request.

Take a look at vroni.com

Subscribe to my newsletter

Get new posts when I publish them.

I respect your privacy. Unsubscribe at any time.

Leave a Reply

Your email address will not be published. Required fields are marked *