Skip to content

Repository files navigation

🎭 Laravel Masquerade

Tests Latest Release License

A feature-rich user impersonation package for Laravel.

Laravel Masquerade lets trusted administrators, support agents, and staff safely sign in as another user while preserving the original account, enforcing permission checks, blocking sensitive actions, limiting impersonation duration, and recording a full audit trail.

use EloquentWorks\Masquerade\Facades\Masquerade;

Masquerade::start(
    target: $user,
    reason: 'Troubleshooting support ticket #1042',
    metadata: [
        'ticket_id' => 1042,
    ],
);

Masquerade::isMasquerading();

Masquerade::stop();

✨ Highlights

  • Secure session-backed user impersonation
  • Original user restoration after masquerade sessions
  • Configurable auth guard support
  • Configurable user model for built-in routes
  • Optional built-in start and stop routes
  • Controller-driven impersonation flow
  • Model-level permission methods
  • Protection against nested impersonation
  • Protection against impersonating yourself
  • Optional required reason before starting a session
  • Session ID regeneration when starting and stopping
  • Configurable max session duration
  • Session extension with optional max-duration caps
  • Automatic expiration middleware
  • Sensitive-route blocking middleware
  • Middleware for routes that require an active masquerade session
  • Request context middleware for support panels and toolbars
  • Full audit logging with actor and target morph relationships
  • Reason, IP address, user agent, metadata, start time, and end time logging
  • Query scopes for action, UUID, impersonator, and target log filtering
  • Optional denied-attempt logging
  • Start, stop, denied, expired, and extended events
  • Blade conditional directive and banner directive
  • Publishable banner view
  • Facade and global helper
  • Install and log pruning Artisan commands
  • Configurable table name, messages, redirects, and routes
  • Laravel Pint, PHPStan/Larastan, PHPUnit, Testbench, and GitHub Actions setup

πŸ“‹ Requirements

Laravel PHP Orchestra Testbench
12.x 8.2+ 10.x
13.x 8.3+ 11.x

πŸ“¦ Installation

composer require eloquent-works/masquerade
php artisan masquerade:install
php artisan migrate

The install command publishes the configuration file, migration, and banner view.

Publish only the configuration file:

php artisan vendor:publish --tag=masquerade-config

Publish only the migration:

php artisan vendor:publish --tag=masquerade-migrations

Publish only the banner view:

php artisan vendor:publish --tag=masquerade-views

Force republishing package assets:

php artisan masquerade:install --force

πŸ‘€ Prepare Your User Model

Add HasMasquerade to the account model that can perform or receive impersonation.

<?php

namespace App\Models;

use EloquentWorks\Masquerade\Traits\HasMasquerade;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Foundation\Auth\User as AuthenticatableUser;

class User extends AuthenticatableUser
{
    use HasMasquerade;

    public function canMasquerade(Authenticatable $target): bool
    {
        return $this->is_admin && ! $this->is($target);
    }

    public function canBeMasqueradedBy(Authenticatable $impersonator): bool
    {
        return ! $this->is_owner;
    }
}

By default, canMasquerade() returns false. This means nobody can masquerade until your application explicitly allows it.

🎭 Start Masquerading

use EloquentWorks\Masquerade\Facades\Masquerade;

Masquerade::start(
    target: $user,
    reason: 'Troubleshooting checkout issue',
    metadata: [
        'ticket_id' => 1042,
        'source' => 'support-panel',
    ],
);

You may also provide a specific impersonator and guard:

Masquerade::start(
    target: $user,
    impersonator: $admin,
    guard: 'web',
    reason: 'QA verification',
);

πŸ›‘ Stop Masquerading

Masquerade::stop();

When stopped, Masquerade logs the original user back in and clears the masquerade session state.

πŸ”Ž Inspect the Current Session

Masquerade::isMasquerading();
Masquerade::hasExpired();
Masquerade::impersonator();
Masquerade::target();
Masquerade::uuid();
Masquerade::reason();
Masquerade::metadata();
Masquerade::startedAt();
Masquerade::expiresAt();
Masquerade::elapsedSeconds();
Masquerade::remainingSeconds();
Masquerade::isMasqueradingAs($user);
Masquerade::isMasqueradedBy($admin);
Masquerade::session();
Masquerade::context();

Update safe session metadata while troubleshooting:

Masquerade::updateMetadata([
    'ticket_status' => 'waiting-on-customer',
]);

Extend the active session when a support task legitimately needs more time:

Masquerade::extend(15, reason: 'Support call is still active');

The helper returns the same manager instance:

masquerade()->isMasquerading();
masquerade()->impersonator();
masquerade()->target();
masquerade()->context();

πŸ›‘οΈ Protect Routes

Block sensitive actions while masquerading:

use Illuminate\Support\Facades\Route;

Route::post('/billing/payment-method', UpdatePaymentMethodController::class)
    ->middleware(['auth', 'masquerade.block']);

Enforce automatic expiration on normal authenticated routes:

Route::middleware(['web', 'auth', 'masquerade.duration'])->group(function (): void {
    Route::get('/dashboard', DashboardController::class);
});

Require an active masquerade session:

Route::get('/support/masquerade-toolbar', SupportToolbarController::class)
    ->middleware(['auth', 'masquerade.required']);

Share masquerade context on the request for toolbars, macros, and support panels:

Route::middleware(['web', 'auth', 'masquerade.context'])->group(function (): void {
    Route::get('/support', SupportDashboardController::class);
});

$context = request()->attributes->get('masquerade.context');

🧭 Built-In Routes

When routes are enabled, Masquerade registers:

POST /masquerade/{user}/start
POST /masquerade/stop

Start from a Blade form:

<form method="POST" action="{{ route('masquerade.start', $user) }}">
    @csrf

    <input
        type="hidden"
        name="reason"
        value="Support ticket #1042"
    >

    <input
        type="hidden"
        name="redirect_to"
        value="{{ route('dashboard') }}"
    >

    <button type="submit">
        Masquerade as {{ $user->name }}
    </button>
</form>

Stop from a Blade form:

<form method="POST" action="{{ route('masquerade.stop') }}">
    @csrf

    <button type="submit">
        Return to my account
    </button>
</form>

Disable built-in routes if you want to provide your own controller flow:

'routes' => [
    'enabled' => false,
],

πŸͺͺ Permissions

Masquerade checks both sides of the impersonation request.

public function canMasquerade(Authenticatable $target): bool
{
    return $this->hasRole('support') && ! $this->is($target);
}

public function canBeMasqueradedBy(Authenticatable $impersonator): bool
{
    return ! $this->hasRole('owner');
}

You may disable model permission methods in the config if your application handles authorization elsewhere:

'permissions' => [
    'use_model_methods' => false,
],

🧾 Audit Logs

Every started, ended, and expired masquerade session can be recorded in masquerade_logs.

use EloquentWorks\Masquerade\Models\MasqueradeLog;

$logs = MasqueradeLog::query()
    ->latest()
    ->get();

Each log may include:

  • Masquerade UUID
  • Action: started, ended, denied, expired, or extended
  • Guard name
  • Impersonator morph type and ID
  • Target morph type and ID
  • Reason
  • IP address
  • User agent
  • Metadata
  • Started timestamp
  • Ended timestamp

Access the actor and target models:

$log->impersonator;
$log->target;

Filter logs with expressive scopes:

MasqueradeLog::query()->started()->latest()->get();
MasqueradeLog::query()->denied()->latest()->get();
MasqueradeLog::query()->extended()->latest()->get();
MasqueradeLog::query()->forMasqueradeUuid($uuid)->get();
MasqueradeLog::query()->forImpersonator($admin)->get();
MasqueradeLog::query()->forTarget($user)->get();

⏱️ Duration Limits

Masquerade can automatically expire old sessions.

'duration' => [
    'enabled' => true,
    'minutes' => 60,
],

Add the duration middleware to routes that should enforce the limit:

Route::middleware(['web', 'auth', 'masquerade.duration'])->group(function (): void {
    // Protected app routes...
});

You can also check expiration manually:

if (Masquerade::stopIfExpired()) {
    // The session was expired and stopped.
}

🚫 Sensitive Route Blocking

Use masquerade.block on routes that should never be available while impersonating another account.

Good examples include:

  • Billing changes
  • Password changes
  • Two-factor authentication changes
  • Email address changes
  • API token creation
  • Account deletion
  • Admin permission changes
Route::middleware(['auth', 'masquerade.block'])->group(function (): void {
    Route::post('/password', UpdatePasswordController::class);
    Route::post('/two-factor-authentication', EnableTwoFactorController::class);
    Route::delete('/account', DeleteAccountController::class);
});

🧩 Blade Directives

Show UI only while masquerading:

@masquerading
    <div class="alert alert-warning">
        You are currently masquerading as another user.
    </div>
@endmasquerading

Render the included banner:

@masqueradeBanner

Or include the published view manually:

@include('masquerade::banner')

πŸ“‘ Events

Masquerade dispatches events you can listen for:

EloquentWorks\Masquerade\Events\MasqueradeStarted::class;
EloquentWorks\Masquerade\Events\MasqueradeEnded::class;
EloquentWorks\Masquerade\Events\MasqueradeDenied::class;
EloquentWorks\Masquerade\Events\MasqueradeExpired::class;
EloquentWorks\Masquerade\Events\MasqueradeExtended::class;

Example listener registration:

use EloquentWorks\Masquerade\Events\MasqueradeStarted;
use Illuminate\Support\Facades\Event;

Event::listen(MasqueradeStarted::class, function (MasqueradeStarted $event): void {
    logger()->info('Masquerade started.', [
        'uuid' => $event->uuid,
        'guard' => $event->guard,
    ]);
});

βš™οΈ Configuration

Publish the configuration file:

php artisan vendor:publish --tag=masquerade-config

Important options:

return [
    'guard' => null,

    'user_model' => env('AUTH_MODEL', 'App\\Models\\User'),

    'session_key' => 'masquerade',

    'routes' => [
        'enabled' => true,
        'prefix' => 'masquerade',
        'middleware' => ['web', 'auth'],
        'name' => 'masquerade.',
        'start_route_parameter' => 'user',
        'redirect_after_start' => '/',
        'redirect_after_stop' => '/',
        'allowed_redirect_hosts' => [],
    ],

    'permissions' => [
        'use_model_methods' => true,
        'impersonator_method' => 'canMasquerade',
        'target_method' => 'canBeMasqueradedBy',
    ],

    'security' => [
        'allow_nested' => false,
        'allow_same_user' => false,
        'require_reason' => false,
        'regenerate_session_id' => true,
        'logout_on_missing_original_user' => true,
    ],

    'duration' => [
        'enabled' => true,
        'minutes' => 60,
        'allow_extension' => true,
        'max_minutes' => 0,
    ],

    'logging' => [
        'enabled' => true,
        'table_name' => 'masquerade_logs',
        'store_ip_address' => true,
        'store_user_agent' => true,
        'log_denied_attempts' => true,
        'retention_days' => 90,
    ],
];

🧰 Commands

php artisan masquerade:install
php artisan masquerade:prune

Examples:

php artisan masquerade:install --force

php artisan masquerade:prune --days=90 --dry-run
php artisan masquerade:prune --days=90 --force

πŸ§ͺ Testing Your App Integration

A simple feature test might look like this:

use App\Models\User;
use EloquentWorks\Masquerade\Facades\Masquerade;
use Illuminate\Support\Facades\Auth;

it('allows support admins to masquerade as users', function (): void {
    $admin = User::factory()->create([
        'is_admin' => true,
    ]);

    $user = User::factory()->create();

    Auth::login($admin);

    Masquerade::start($user, reason: 'Support ticket');

    expect(Masquerade::isMasquerading())->toBeTrue();
    expect(Auth::user()->is($user))->toBeTrue();

    Masquerade::stop();

    expect(Auth::user()->is($admin))->toBeTrue();
});

πŸ“ Session Notes

Masquerade::addNote(
    note: 'Checked the customer billing page.',
    metadata: [
        'ticket_id' => 'SUP-1042',
    ],
);

$notes = Masquerade::notes();

🎟️ Ticket Context

Masquerade::start(
    target: $user,
    reason: 'Troubleshooting checkout issue.',
    metadata: [
        'ticket_id' => 'SUP-1042',
        'ticket_url' => 'https://support.example.com/tickets/SUP-1042',
    ],
    category: 'support',
);

Masquerade::ticketId();
Masquerade::ticketUrl();
Masquerade::contextValue('ticket_id');

πŸ›‘οΈ Ability Blocking

Block sensitive actions while masquerading:

Masquerade::assertAbilityAllowed('billing.update');

Or use middleware:

Route::post('/billing', UpdateBillingController::class)
    ->middleware(['auth', 'masquerade.ability:billing.update']);

πŸ“€ Audit Export

php artisan masquerade:export --format=csv
php artisan masquerade:export --format=json
php artisan masquerade:export --uuid=00000000-0000-0000-0000-000000000000

🚨 Risk Detection

Enable risk detection in config:

'risk' => [
    'enabled' => true,
    'max_sessions_per_hour' => 10,
    'max_denied_attempts_per_hour' => 5,
    'max_blocked_abilities_per_hour' => 3,
    'score_threshold' => 1,
],

Risk detections are logged with:

action = risk_detected
risk_score
risk_flags

πŸ“š Documentation

Full documentation is available in the docs directory:

βœ… Quality Checks

composer validate --strict
composer quality

Or run the tools separately:

composer format
composer analyse
composer test

πŸ” Security

Only allow highly trusted users to masquerade. Always require authorization before starting a session, consider requiring a reason, and block sensitive account, billing, authentication, and destructive routes with masquerade.block.

Masquerade regenerates the session ID when starting and stopping by default. Keep that enabled unless your application has a specific reason to disable it.

🀝 Contributing

See CONTRIBUTING.md and CODE_OF_CONDUCT.md.

πŸ™ Credits

Built by Eloquent Works.

πŸ“„ License

Laravel Masquerade is open-source software licensed under the MIT License.

About

A feature-rich Laravel impersonation package with secure session handling, audit logs, middleware, events, and Blade helpers.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Contributors

Languages