Introduction
Impersonate is quite a handy feature in some applications. It allows you to choose from a list of users and to impersonate them, in order to see the application from their point of view, without having to log out and log in again. For example, as an administrator you want to recreate a bug encountered by one of your users, without having them to share their password with you.
It’s a functionality that it’s really powerful, but at the same time it’s easy to implement in Laravel. You just need to make sure that a normal user cannot impersonate an administrator.
Meet the impersonate Middleware
“HTTP middleware provide a convenient mechanism for filtering HTTP requests entering your application.”
Middleware are additional layers that enclose the application logic, allowing modifications on the requests and responses of the application.
For example, Laravel uses a middleware that verifies if the user of your application is authenticated. If not, the middleware will redirect the user to the login screen.
We can use the same logic to login as a different user for each request. Laravel provides the Auth::once()
method to log a user into the application without sessions or cookies, however it requires an array of credentials, which we don’t have. The solution is to use the Auth::onceUsingId(mixed $id)
method, which serves the same purpose, but it requires only the user ID.
<?php
class Impersonate
{
/**
* Handle an incoming request.
*/
public function handle($request, Closure $next)
{
if($request->session()->has('impersonate'))
{
Auth::onceUsingId($request->session()->get('impersonate'));
}
return $next($request);
}
}
As you can see, if the session has an impersonate
value, which contains the ID of the user that we want to impersonate, our middleware starts working, logging a different user for the request.
Next we have to register our new middleware to be available for our routes. You can add it into the $routeMiddleware
array inside your App\Http\Kernel.php
file.
<?php
protected $routeMiddleware = [
'auth' => \App\Http\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'impersonate' => \App\Http\Middleware\Impersonate::class
];
Now we have to set up two routes in our application. One for impersonating a user and one for stopping the impersonation.
For example, in our UserController.php
we can have something like this:
<?php
public function impersonate($id)
{
$user = User::find($id);
// Guard against administrator impersonate
if(! $user->isAdministrator())
{
Auth::user()->setImpersonating($user->id);
}
else
{
flash()->error('Impersonate disabled for this user.');
}
return redirect()->back();
}
public function stopImpersonate()
{
Auth::user()->stopImpersonating();
flash()->success('Welcome back!');
return redirect()->back();
}
Notice: here I’m using the wonderful flash library developed by Jeffrey Way to flash notifications in the view.
Now we can declare these routes in our routes.php
file
<?php
Route::get('/users/{id}/impersonate', 'UserController@impersonate');
Route::get('/users/stop', 'UserController@stopImpersonate');
or, if you prefer, you can use POST requests to avoid accidental calls to those routes. In every case make sure that the first route is accessible only to an authenticated user which has to be an administrator.
Now we have to add a couple of additional functions to the User
model. As you can see, these are only descriptive methods that interact with the Session object.
<?php
class User extends Authenticatable
{
public function setImpersonating($id)
{
Session::put('impersonate', $id);
}
public function stopImpersonating()
{
Session::forget('impersonate');
}
public function isImpersonating()
{
return Session::has('impersonate');
}
}
Crystal clear!
Finally you just need to add the middleware for the routes that you want to enable to impersonated users:
<?php
Route::group(['middleware' => 'impersonate'], function()
{
// ...
});
You can also use the helper function isImpersonating
to add a little notification in the navbar, for example:
<ul class="nav navbar-nav navbar-right">
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">{{ Auth::user()->name }} <span class="caret"></span></a>
<ul class="dropdown-menu">
@if(Auth::user()->isImpersonating())
<li><a href="{{ action('UserController@stopImpersonate') }}">Stop Impersonate</a></li>
@endif
<li><a href="logout">Logout</a></li>
</ul>
</li>
</ul>