Impersonating Users in Laravel

Sometimes we face situations when all the tests are passing, we find no bugs, but still, on our users’ part, something broke. By impersonating our users, we can see what they see and track the bugs down easily.

The Basic Concept

Running an application is a permanent job.  So it’s important to have the tools, what makes our workflow easier and more precise. Impersonating users is a good way to filter the errors/problems out on our user’s end, by seeing what the user sees. Impersonation is a visual thing; we use the browser for that.

So what we want in this case, to impersonate users and revert to our session quickly when we want. Then let’s prepare the routes and the controller for this!

The Routes

We need two routes, one for impersonation and another for reverting to our original account. Both routes hit different actions from the same controller. Also, it would be good, to bind some middlewares where we check if the user can impersonate other users or we don’t let a user impersonate itself. But we bind the middlewares in the controller constructor and not directly on the routes.

// routes/web.php

Route::prefix('impersonation')->group(function ($router) {
    # Revert route...
    $router->get('revert', '[email protected]')->name('impersoante.revert');
    # Impersonate route...
    $router->get('{user}', '[email protected]')->name('impersonate.impersonate');
});
Note that, we need to define the revert route before. If the {user} wildcard would be the first, it would handle the ‘revert’ keyword as a user id and it would return a 404 error. If we define our wildcarded route after the fixed one, we can prevent this behavior.

Our routes are ready to go, let’s take a look at our controller, where we have the main action.

The Controller

With artisan, it’s easy to generate our controllers by running the command php artisan make:controller. After we generated our ImpersonateController, we need to implement our actions what we defined in our routes before.

Let’s see the code; then we give some explanation to the actions.

// app/Http/Controllers/ImpersonateController.php

<?php 

namespace App\Http\Controllers; 

use App\User; 

class ImpersonateController extends Controller 
{ 
    /** 
     * Create a new controller instance. 
     * 
     * @return void 
     */ 
    public function __construct() 
    { 
        $this->middleware('auth');
        // $this->middleware('can:impersonate');
    }

    /**
     * Impersonate the given user.
     *
     * @param  \App\User  $user
     * @return \Illuminate\Http\Response
     */
    public function impersoante(User $user)
    {
        if ($user->id !== ($original = Auth::user()->id)) {
            session()->put('original_user', $original);

            auth()->login($user);
        }

        return redirect('/home');
    }

    /**
     * Revert to the original user.
     *
     * @return \Illuminate\Http\Response
     */
    public function revert()
    {
        auth()->loginUsingId(session()->get('original_user'));

        session()->forget('original_user');

        return redirect('/home');
    }
}

In the impersonate() method, we are using route-model binding to retrieve the user we want to use. Then we check if the user matches the authenticated user or not. If you want you can refactor this to a policy or a middleware as well, this is not the cleanest way to do this check but does it for now. Then we store the old user’s id in the session and logging in the user we retrieved. After everything is ok, we redirect to the home page, but now we are in impersonation mode.

The revert() method does the opposite. We are logging in the original user we had, by getting its ID from the session. Then we remove the data from the session and redirect home again.

Summary

A quick note: while impersonation (at least the way we do here) is session based, we can do it in routes/controllers what belongs to the web middleware group. It’s because API routes are stateless, they are using API tokens and not a session for authentication and authorization.

It’s always risky to see other users data, so it’s important to prevent any abuse. As you see we have a middleware in the controller’s constructor. It’s just an indication what you can have to protect yourself. You can work with policies or middlewares as well, the point is, do not let unauthorized users see what they should not.

Also, we should indicate that we are impersonating and not in our original account. We can do that by check if the session has the key what contains the original user’s id. It’s a nice way to prevent any actions what we would do by forgetting we are in impersonation mode.