The go-to PHP IDE with extensive out-of-the-box support for Laravel and its ecosystem.

Mirror

Mirror stats

Downloads
1
Stars
24
Open Issues
0
Forks
1

View on GitHub →

Mirror is a Laravel package that handles user impersonation.

Mirror Logo
Latest Version on Packagist GitHub Tests Action Status Total Downloads License

Mirror

Mirror is an elegant user impersonation package for Laravel. It allows administrators to seamlessly log in as other users to troubleshoot issues, provide support, or test user experiences. Mirror handles session integrity with cryptographic verification, automatic expiration, multi-guard support, flexible middleware, and lifecycle events for audit logging. Perfect for production applications that need reliable and secure user impersonation.

Features

  • HMAC-SHA256 session integrity to prevent tampering
  • Configurable TTL expiration
  • Middleware for access control and TTL enforcement
  • Multi-guard support
  • Flexible URL redirection
  • Lifecycle events for audit logging

Requirements

  • PHP 8.2+
  • Laravel 11+

Installation

composer require franbarbalopez/mirror

Optional - publish the config file:

php artisan vendor:publish --tag=mirror

Quick Start

1. Add Trait to User Model

use Illuminate\Foundation\Auth\User as Authenticatable;
use Mirror\Concerns\Impersonatable;
 
class User extends Authenticatable
{
use Impersonatable;
 
public function canImpersonate(): bool
{
return $this->hasRole('admin');
}
 
public function canBeImpersonated(): bool
{
return ! $this->hasRole('super-admin');
}
}

Important: If you don't implement canImpersonate(), everyone can impersonate everyone. The trait returns true by default.

2. Start Impersonating

use Mirror\Facades\Mirror;
 
public function impersonate(User $user)
{
Mirror::start($user);
 
return redirect()->route('dashboard');
}

3. Stop Impersonating

public function leave()
{
Mirror::stop();
 
return redirect()->route('admin.users.index');
}

Security

Impersonation sessions are protected with HMAC-SHA256 hashes using your app key. The hash covers the impersonator ID, guard name, start time, and redirect URL. On every stop() call, Mirror verifies this hash - if someone's tampered with the session, it throws an exception and clears everything.

Configure TTL in config/mirror.php to automatically expire sessions after a set time.

API Reference

Starting Impersonation

By user instance:

Mirror::start($user);
 
// With redirect URLs
$redirectUrl = Mirror::start(
user: $targetUser,
leaveRedirectUrl: route('admin.users.index'),
startRedirectUrl: route('dashboard')
);
 
return redirect($redirectUrl);

By primary key (works with int, UUID, ULID, etc.):

Mirror::startByKey(123);
 
Mirror::startByKey('550e8400-e29b-41d4-a716-446655440000');

By email:

Mirror::startByEmail('user@example.com');

Stopping Impersonation

Mirror::stop();
 
// Force stop - bypasses TTL check but still verifies integrity
Mirror::forceStop();

Use forceStop() when you need to end impersonation from admin actions or cleanup scripts - it skips the TTL check but still throws if the session's been tampered with.

Checking State

Mirror::isImpersonating(): bool
Mirror::getImpersonator(): ?Authenticatable
Mirror::impersonatorId(): int|string|null
Mirror::getLeaveRedirectUrl(): ?string

Aliases

Mirror::as($user); // same as start()
Mirror::leave(); // same as stop()
Mirror::impersonating(); // same as isImpersonating()
Mirror::impersonator(); // same as getImpersonator()

Middleware

mirror.ttl

Checks if the impersonation session has expired and automatically calls stop() if needed:

Route::middleware('mirror.ttl')->group(function () {
Route::get('/admin/users', [UserController::class, 'index']);
Route::get('/admin/users/{user}', [UserController::class, 'show']);
});

Good for protecting sensitive admin areas where you want expired sessions to exit gracefully. Note that when TTL expires, this middleware will end the impersonation and redirect, so make sure your session cleanup is set up properly.

mirror.require

Only allows access if actively impersonating:

Route::middleware('mirror.require')->group(function () {
Route::get('/impersonation/banner', function () {
return view('impersonation.banner');
});
});

Useful for special UI components that only make sense during impersonation - like a banner showing who you're impersonating.

mirror.prevent

Blocks access while impersonating:

Route::middleware('mirror.prevent')->group(function () {
Route::post('/admin/users/{user}/delete', [UserController::class, 'destroy']);
Route::get('/admin/settings', [SettingsController::class, 'edit']);
});

Protects destructive actions or sensitive settings that should only be accessed as the original user, not while impersonating someone else.

Authorization

The Impersonatable trait provides two methods that both return true by default. Override them to add your own logic:

use Mirror\Concerns\Impersonatable;
 
class User extends Authenticatable
{
use Impersonatable;
 
public function canImpersonate(): bool
{
return $this->hasRole('admin');
}
 
public function canBeImpersonated(): bool
{
return ! $this->hasRole('super-admin');
}
}

You don't need the trait - Mirror will look for these methods on your user model regardless:

class User extends Authenticatable
{
public function canImpersonate(): bool
{
return $this->hasPermission('impersonate-users');
}
 
public function canBeImpersonated(): bool
{
return ! $this->is_system_account;
}
}

URL Redirection

You can control where users go when starting and stopping impersonation:

public function impersonate(User $user)
{
$redirectUrl = Mirror::start(
user: $user,
leaveRedirectUrl: route('admin.users.index'), // where to go when they stop
startRedirectUrl: route('dashboard') // where to go right now
);
 
return redirect($redirectUrl);
}
 
public function leave()
{
Mirror::stop();
 
return redirect(Mirror::getLeaveRedirectUrl());
}

If you don't specify leaveRedirectUrl, it defaults to the current URL where start() was called.

Events

Mirror dispatches two events you can listen to:

  • Mirror\Events\ImpersonationStarted
  • Mirror\Events\ImpersonationStopped

Both events contain the impersonator, the target user, and the guard name. Good for audit logs or triggering workflows.

Events are dispatched after the response is sent to the client, ensuring that critical impersonation operations complete without delay. This is especially important for middleware like mirror.ttl that may run on every request.

use Mirror\Events\ImpersonationStarted;
 
Event::listen(ImpersonationStarted::class, function (ImpersonationStarted $event) {
// Log the activity to your audit system of choice
Log::info('User impersonation started', [
'impersonator_id' => $event->impersonator->id,
'impersonated_id' => $event->impersonated->id,
'guard' => $event->guardName,
]);
});

Performance & Optimization

Mirror is optimized for high-performance applications:

Request-Scoped Caching

The impersonator model is cached within a single request to avoid redundant database queries:

// This first call will query the database
$impersonator = Mirror::getImpersonator();
 
// Subsequent calls in the same request use the cached instance, therefore this one will not:
$impersonator = Mirror::getImpersonator();

This is particularly beneficial for middleware like mirror.ttl that run on every request.

Deferred Event Dispatching

Impersonation events are dispatched after the response is sent to the client, ensuring that event listeners don't impact response time. This keeps your request cycle fast while still allowing audit logging and other background tasks.

Multi-Guard Support

Mirror automatically detects which guard you're using:

Auth::guard('admin')->login($admin);
 
Mirror::start($user); // uses 'admin' guard
 
Mirror::stop(); // restores to 'admin' guard

You don't need to specify the guard manually - it figures it out from the current auth context.

Blade Directives

@impersonating

@impersonating
<div class="alert">
You're impersonating {{ auth()->user()->name }}.
<a href="{{ route('impersonation.leave') }}">Exit</a>
</div>
@endimpersonating
 
{{-- Check specific guard --}}
@impersonating('admin')
<div>Impersonating via admin guard</div>
@endimpersonating

@canImpersonate

@canImpersonate
<a href="{{ route('admin.users.index') }}">Manage Users</a>
@endcanImpersonate
 
{{-- With guard --}}
@canImpersonate('admin')
<div>Admin tools</div>
@endcanImpersonate

@canBeImpersonated

{{-- Check current user --}}
@canBeImpersonated
<span>Available for support</span>
@endcanBeImpersonated
 
{{-- Check specific user --}}
@canBeImpersonated($user)
<form method="POST" action="{{ route('impersonation.start', $user) }}">
@csrf
<button>Impersonate</button>
</form>
@endcanBeImpersonated
 
{{-- With guard --}}
@canBeImpersonated($user, 'admin')
<button>Login as this user</button>
@endcanBeImpersonated

License

MIT. See LICENSE.md.

Credits

Developed by franbarbalopez.

Cube

Laravel Newsletter

Join 40k+ other developers and never miss out on new tips, tutorials, and more.


Franbarbalopez Mirror Related Articles

Impersonation in Laravel with the Mirror Package image

Impersonation in Laravel with the Mirror Package

Read article
Laravel Cloud logo

Laravel Cloud

Easily create and manage your servers and deploy your Laravel applications in seconds.

Laravel Cloud
Acquaint Softtech logo

Acquaint Softtech

Acquaint Softtech offers AI-ready Laravel developers who onboard in 48 hours at $3000/Month with no lengthy sales process and a 100 percent money-back guarantee.

Acquaint Softtech
PhpStorm logo

PhpStorm

The go-to PHP IDE with extensive out-of-the-box support for Laravel and its ecosystem.

PhpStorm
Harpoon: Next generation time tracking and invoicing logo

Harpoon: Next generation time tracking and invoicing

The next generation time-tracking and billing software that helps your agency plan and forecast a profitable future.

Harpoon: Next generation time tracking and invoicing
Kirschbaum logo

Kirschbaum

Providing innovation and stability to ensure your web application succeeds.

Kirschbaum
Lucky Media logo

Lucky Media

Get Lucky Now - the ideal choice for Laravel Development, with over a decade of experience!

Lucky Media