How to Add Impersonate Functionality to Your Backpack v4 Admin Panel

Posted in Tutorials by Cristian Tabacitu - 16th of October 2019

Sometimes it's useful for the admin to see everything from the user's perspective. To see exactly what he sees. With an admin panel like Backpack, it's not too difficult to add this functionality.

Assumptions:

  • you're using PermissionManager (or have a custom UserCrudController);
  • you have a way to differentiate between admins and normal users;
  • you're running Backpack v4 on Laravel 5.8 or Laravel 6;
  1. Let's add a trait that allows the user to impersonate and stop impersonating another user. It can be as simple as storing the impersonated user id in the session. Create app\Http\Models\Traits\CanImpersonateTrait.php:
<?php namespace App\Models\Traits;

use Illuminate\Database\Eloquent\Model;
use Session;

trait CanImpersonateTrait
{
    public function setImpersonating($id)
    {
        Session::put('impersonate', $id);
    }

    public function stopImpersonating()
    {
        Session::forget('impersonate');
    }

    public function isImpersonating()
    {
        return Session::has('impersonate');
    }
}
  1. Of course, you need to use the trait above on your User.php model:
class User extends Authenticatable
{
    use Notifiable;
    use CrudTrait;
    use HasRoles;
    use \App\Models\Traits\CanImpersonateTrait;
  1. Once your admin is impersonating someone, he/she should be able to stop impersonating. This needs a new button in the menu:
@if (Auth::user()->isImpersonating())
    <li><a href="{{ url('stop-impersonating') }}">Stop Impersonating</a></li>
@endif

and a route to go with it (inside your auth middleware or wherever else you have your logged in routes):

    Route::get('stop-impersonating', function() {
        backpack_user()->stopImpersonating();
        \Alert::success('Impersonating stopped.')->flash();
        return redirect()->back();
    });
  1. Now that we have a way to STOP the impersonation, let's have a way to START the impersonation. The most intuitive place to start impersonation would be to have button next to the User inside the Users CRUD. If you're using PermissionManager, you can extend the UserCrudController by creating a routes/backpack/permissionmanager.php file, where you copy-paste the contents of the package routes, and comment out the route to the UserCrudController:
<?php

/*
|--------------------------------------------------------------------------
| Backpack\PermissionManager Routes
|--------------------------------------------------------------------------
|
| This file is where you may define all of the routes that are
| handled by the Backpack\PermissionManager package.
|
*/

Route::group([
        'namespace'  => 'Backpack\PermissionManager\app\Http\Controllers',
        'prefix'     => config('backpack.base.route_prefix', 'admin'),
        'middleware' => ['web', backpack_middleware()],
], function () {
    Route::crud('permission', 'PermissionCrudController');
    Route::crud('role', 'RoleCrudController');
    // Route::crud('user', 'UserCrudController'); // removed and placed within the backpack/custom routes files
});

Next let's add that route to our routes/backpack/custom.php:

// ... inside Route::group() and everything so it's only shown to admins
    // Overwritten CRUD packages
    Route::crud('user', 'UserCrudController');

This will point the route to a controller we create now, app/Http/Controllers/Admin/UserCrudController.php:

<?php

namespace App\Http\Controllers\Admin;

use Backpack\CRUD\app\Http\Controllers\CrudController;
use Backpack\PermissionManager\app\Http\Controllers\UserCrudController as OriginalUserCrudController;
use App\Http\Controllers\Admin\Operations\ImpersonateOperation;
// VALIDATION: change the requests to match your own file names if you need form validation
use App\Http\Requests\ArticleRequest as StoreRequest;
use App\Http\Requests\ArticleRequest as UpdateRequest;

class UserCrudController extends OriginalUserCrudController
{
    use ImpersonateOperation;
}
  1. Now that the UserCrudController uses the ImpersonateOperation, let's create that operation - inside app/Http/Controllers/Admin/Operations/ImpersonateOperation.php:
<?php

namespace App\Http\Controllers\Admin\Operations;

use Illuminate\Support\Facades\Route;
use Session;
use Alert;

trait ImpersonateOperation
{
    /**
     * Define which routes are needed for this operation.
     *
     * @param string $segment    Name of the current entity (singular). Used as first URL segment.
     * @param string $routeName  Prefix of the route name.
     * @param string $controller Name of the current CrudController.
     */
    protected function setupImpersonateRoutes($segment, $routeName, $controller)
    {
        Route::get($segment.'/{id}/impersonate', [
            'as'        => $routeName.'.impersonate',
            'uses'      => $controller.'@impersonate',
            'operation' => 'impersonate',
        ]);
    }

    /**
     * Add the default settings, buttons, etc that this operation needs.
     */
    protected function setupImpersonateDefaults()
    {
        $this->crud->allowAccess('impersonate');

        $this->crud->operation('impersonate', function () {
            $this->crud->loadDefaultOperationSettingsFromConfig();
        });

        $this->crud->operation('list', function () {
            $this->crud->addButton('line', 'impersonate', 'view', 'crud::buttons.impersonate');
        });
    }

    /**
     * Impersonate that user and redirect to his profile.
     *
     * @return Response
     */
    public function impersonate()
    {
        $this->crud->hasAccessOrFail('impersonate');

        $entry = $this->crud->getCurrentEntry();
        
        backpack_user()->setImpersonating($entry->id);

        Alert::success('Impersonating '.$entry->name.' (id '.$entry->id.').')->flash();

        // load the view
        return redirect('getting-started');
    }
}
  1. Notice the operation adds a button to the list view, so let's go ahead and create it inside resources/views/vendor/backpack/crud/buttons/impersonate.blade.php:
@if ($crud->hasAccess('impersonate'))
	<a href="{{ url($crud->route.'/'.$entry->getKey().'/impersonate') }} " class="btn btn-sm btn-link {{ backpack_user()->id == $entry->getKey()? 'disabled': '' }}"><i class="fa fa-user"></i> Impersonate</a>
@endif
  1. Now that we have ways to toggle the impersonation on/off, there's only one thing left to do - actually change the logged in admin's ID for the user you want to impersonate. We have the information in the session, we only need to use it. And the easiest way would be using a middleware. So let's create app/Http/Middleware/Impersonate.php:
<?php

namespace App\Http\Middleware;

use Closure;

class Impersonate
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        if ($request->session()->has('impersonate') && backpack_user()->hasRole('superadmin')) {
            backpack_auth()->onceUsingId($request->session()->get('impersonate'));
        }

        return $next($request);
    }
}

Notice above we've also checked that the admin has a superadmin role (to only allow them to impersonate users). You might wish to change that to a permission, or restrict it based on ID to only allow a few admins to impersonate other users.

  1. Now that we have the middleware, let's register it, by adding it to our app\Http\Kernel.php's $routeMiddleware array:

    /**
     * The application's route middleware.
     *
     * These middleware may be assigned to groups or used individually.
     *
     * @var array
     */
    protected $routeMiddleware = [
       //...
        'impersonate' => \App\Http\Middleware\Impersonate::class,
    ];
  1. And now let's use the middleware on the routes where we want the admin to be able to impersonate other users. Most likely your logged-in routes are grouped under an auth middleware inside routes/web.php - you can now just use the impersonate middleware along with auth. Alternatively, if you want ALL routes to have the impersonate feature, you could add this middleware inside your app/Http/Kernel.php's $middlewareGroups, under web.

That's it. It wasn't such a difficult feature to build, was it? Keep in mind this is a functionality that few admin panels have, and now - you have it :-)

In the process we've:

  • added impersonating functionality to our User model;
  • added a menu item to stop impersonating someone;
  • extended PermissionManager to use our UserCrudController instead of the one in the package;
  • created a new operation for UserCrudController, to add a button next to each User line, to impersonate that user;
  • created a custom CRUD button;
  • created a middleware that forces the logged in ID to be the impersonated user;
  • used that middleware on the routes where impersonation is enabled;

Thanks for reading & following. Cheers!