Ghost is a user impersonation package that allows administrators to seamlessly log in as other users to troubleshoot issues, provide support, or test user experiences without knowing their passwords.
- HMAC-SHA256 Session Integrity: Prevents session tampering while impersonating.
- Configurable TTL: Automatically ends impersonation sessions after a set duration.
- Role-Based Authorization: Only users with allowed roles can impersonate, and protected roles cannot be impersonated.
- Activity Logging: Built-in listeners log all impersonation events to the activity table.
- Nested Impersonation Prevention: Cannot impersonate while already impersonating.
- Lifecycle Events: Dispatches
ImpersonationStartedandImpersonationStoppedevents. - Fluent API: Simple
Ghostfacade for common operations.
Your User model must implement a hasRole(string $role): bool method:
// App\Models\User
public function hasRole(string $role): bool
{
return $this->role === $role;
}Ghost can be installed as a framework package:
php dock package:install Ghost --packagesThe dock CLI automates several steps to ensure the package is ready for use:
- Configuration Publishing: Copies the package configuration to
App/Config/ghost.php. - Provider Registration: Registers
Ghost\Providers\GhostServiceProviderin the application kernel. - Middleware Setup: Automatically injects
Ghost\Middleware\ImpersonateMiddlewareinto thewebmiddleware stack. - Event Discovery: Enables the framework to discover ghost-related events and listeners.
use App\Models\User;
use Ghost\Ghost;
$user = User::find(123);
if (Ghost::impersonate($user)) {
// Success: Now $this->auth->user() returns user 123
} else {
// Failed: No permission, protected user, or already ghosting
}Ghost::stop();
// Now $this->auth->user() returns your original accountif (Ghost::isGhosting()) {
$impersonator = Ghost::getImpersonator(); // Original admin
$impersonated = Ghost::getImpersonated(); // User being viewed
if (Ghost::isExpired()) {
Ghost::stop(); // Session TTL exceeded
}
}Located at App/Config/ghost.php:
return [
// Session duration in seconds (default: 1 hour)
'ttl' => env('GHOST_TTL', 3600),
// Session key for storing ghost state
'session_key' => 'anchor_ghost_impersonation',
// Roles that cannot be impersonated (e.g., super-admins)
'protected_roles' => ['super-admin'],
// Roles allowed to impersonate (if User has hasRole() method)
'allowed_roles' => ['admin', 'super-admin'],
];Ghost implements a layered authorization system:
You cannot start a new impersonation while already impersonating someone.
If no permission system exists but hasRole() is available, Ghost checks against allowed_roles:
// In your User model
public function hasRole(string $role): bool
{
return $this->role === $role;
}Users with roles in the protected_roles config array cannot be impersonated by anyone.
Ghost uses a signed session payload with HMAC-SHA256 to prevent tampering:
| Field | Purpose |
|---|---|
impersonator_id |
Original admin's user ID |
impersonated_id |
Target user's ID |
original_token |
Original session token for restoration |
expires_at |
Unix timestamp when session expires |
signature |
HMAC-SHA256 hash using encryption_key |
The default Ghost\Middleware\ImpersonateMiddleware is provided to automatically maintain and validate the impersonation state across requests. It terminates sessions if the signature is invalid or the TTL has expired.
For custom application logic (like blocking specific actions), you can implement your own middleware as shown in the Implementation section.
Ghost automatically logs all impersonation events to the activity table via built-in listeners, provided the Activity package is installed:
LogImpersonationStartedListener: Records when admin starts impersonatingLogImpersonationStoppedListener: Records when impersonation ends
Example activity log entry:
User: Admin (ID: 1)
Description: Started impersonating user #123 (john@example.com)
Ghost dispatches events for custom integrations:
use Ghost\Events\ImpersonationStartedEvent;
use Ghost\Events\ImpersonationStoppedEvent;
// Both events contain:
$event->impersonator; // User who initiated impersonation
$event->impersonated; // User being impersonateduse App\Models\User;
use App\Core\BaseController;
use Ghost\Ghost;
use Helpers\Http\Response;
class ImpersonateController extends BaseController
{
public function impersonate(?string $refid = null): Response
{
$user = User::findByRefid($refid);
if (!$user) {
$this->flash->error('User not found.');
return $this->response->redirect($this->request->callback());
}
if (!Ghost::impersonate($user)) {
$this->flash->error('Cannot impersonate this user.');
return $this->response->redirect($this->request->callback());
}
$this->flash->success("Now viewing as {$user->name}");
return $this->response->redirect('dashboard');
}
public function stop(): Response
{
Ghost::stop();
$this->flash->success('Returned to your account.');
return $this->response->redirect('admin/users');
}
}Prevent impersonators from performing destructive actions:
// In a controller that handles account deletion
public function destroy(): Response
{
if (Ghost::isGhosting()) {
$this->flash->error('Cannot delete account while impersonating.');
return $this->response->redirect($this->request->callback());
}
// Proceed with deletion...
}namespace App\Middleware;
use Closure;
use Core\Middleware\MiddlewareInterface;
use Ghost\Ghost;
use Helpers\Http\Request;
use Helpers\Http\Response;
class PreventGhostActionsMiddleware implements MiddlewareInterface
{
private array $blockedRoutes = [
'account/delete',
'billing/cancel'
];
public function handle(Request $request, Response $response, Closure $next): mixed
{
if (Ghost::isGhosting() && in_array($request->route(), $this->blockedRoutes)) {
return $response->json([
'error' => 'This action is not allowed while impersonating'
], 403);
}
return $next($request, $response);
}
}The Ghost package follows the Anchor Framework's Internal Package Architecture. Unlike standalone libraries, it is designed for deep integration with the core:
Config/: Default settings (published during installation).Events/ & Listeners/: Built-in lifecycle hooks and activity logging.Middleware/: Core security and TTL validation.Providers/: Automated registration with the IoC container.setup.php: The "installation manifest" that tells the CLI how to configure the package.
- Zero-Configuration Experience: Standard standalone packages often require manual provider and middleware registration. Anchor's
setup.phpallows thedockCLI to perform these actions automatically. - Architectural Consistency: By mirroring the application's structure within the package, developers can seamlessly transition between building application features and framework extensions.
- Efficiency: Centralized testing and discovery make the package faster to load and easier to maintain within a monorepo-style environment.