Impersonation is a useful tool: it allows an administrator to view the website as if they were logged in as another user, but without having to know their password. The Laravel documentation implies that you can do this using Auth::loginUsingId()
, but you will find that this doesn’t work.
Authentication Flow
Laravel’s authentication system contains two main parts:
- Providers supply a database of user accounts.
- Guards authenticate the user with each HTTP request.
It is the guards that we are interested in. The default guard is Illuminate\Auth\SessionGuard
, which stores the user’s ID in the session. When the user requests a new page, their ID is read from the session variable.
Guards are configured in config/auth.php
. The guard for web routes is called web
.
Sanctum adds two more guards:
- API Token Guard is for API requests, and uses a token in the
Authorization
HTTP header. - SPA Authentication Guard is for web applications, and is the one you will be using for impersonation.
The Sanctum is used by adding the auth:sanctum
middleware to your routes. This tells it to load the ‘auth’ middleware (App\Http\Middleware\Authenticate
, which extends Illuminate\Auth\Middleware\Authenticate
) with the ‘sanctum’ guard (Illuminate\Auth\RequestGuard
with a callback into Laravel\Sanctum\Guard
).
The flow is as follows:
- The page request invokes the
auth
middlewareApp\Http\Middleware\Authenticate
. - This calls
Illuminate\Auth\Middleware\Authenticate
with thesanctum
guard. - The
sanctum
guard is anIlluminate\Auth\RequestGuard
with a callback ofLaravel\Sanctum\Guard
Laravel\Sanctum\Guard
tries to authorize with thesanctum.guard
guard, which by default is the same as theweb
guard, which isIlluminate\Auth\SessionGuard
. This checks to see if you have a variablelogin_[guard name]_[guard hash]
in your session indicating that you are already logged in.- If the
web
guard cannot authenticate the user then it checks for anAuthorization
header. This step is only useful in API requests.
A Spanner in the Works
After setting the new user account, you may find that you become logged out. This is due to the middleware Illuminate\Session\Middleware\AuthenticateSession
(or \Laravel\Jetstream\Http\Middleware\AuthenticateSession
if you are using Jetstream).
The AuthenticateSession
middleware handles the “log out other devices” functionality. It does this by storing a hash of the user’s password in the session, and logging the user out if their current password hash does not match the password hash in the session. Pressing the “log out other devices” button simply rehashes the user’s password.
The password hash is stored in the session after the controller runs, so it is possible to update it in your impersonation controller. This is shown in the next section.
Making Impersonation Work
Below is an example HTTP controller. It has two methods: impersonate()
to change to a different user, and unimpersonate()
to return to your original user account. While you are impersonating another user, your original user ID is stored in the session under impersonatingFrom
.
<?php
namespace App\Http\Controllers;
use App\Models\User;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
/**
* Handle user impersonation.
*/
class ImpersonationController extends Controller
{
/**
* Impersonate a user.
*
* @param Request $request The HTTP request.
* @param User $user The user to impersonate.
*
* @return RedirectResponse Redirects to the dashboard.
*/
public function impersonate(Request $request, User $user): RedirectResponse
{
// Make sure that the user is allowed to impersonate other users.
// Use whatever is appropriate for your system.
// $this->authorize('impersonate', User::class);
// Gate::allowIf(fn (User $user) => $user->isAdministrator());
// ...
// Get the ID of the currently logged in user.
$originalUserId = auth()->user()->id;
// Log in as the user to impersonate.
auth('web')->loginUsingId($user->id);
// Reset the user for the AuthenticateSession middleware.
$request->setUserResolver(fn () => $user);
// Record the original user ID so we can unimpersonate later.
$request->session()->put('impersonatingFrom', $originalUserId);
return redirect()->route('dashboard');
}
/**
* Stop impersonating a user.
*
* @param Request $request The HTTP request.
*
* @return RedirectResponse Redirects to the dashboard.
*/
public function unimpersonate(Request $request): RedirectResponse
{
$userId = $request->session()->get('impersonatingFrom');
if ($userId) {
// Fetch the original user.
$user = User::findOrFail($userId);
// Switch to the original user account.
auth('web')->loginUsingId($user->id);
// Reset the user for the AuthenticateSession middleware.
$request->setUserResolver(fn () => $user);
// Remove the impersonation information from the session.
$request->session()->forget('impersonatingFrom');
}
return redirect()->route('dashboard');
}
}
The actual impersonation is done by these two lines:
auth('web')->loginUsingId($user->id);
$request->setUserResolver(fn () => $user);
The first line tells the web
guard to switch to the new user, given the new user’s ID.
The second line sets the user that Illuminate\Session\Middleware\AuthenticateSession
will use when storing the password hash in the session. This prevents it from logging you out on the next page load.