Impersonating Users on a JSON API (With FOS User Bundle)


In the previous video we covered how to use the new stateless user impersonation mode available in Symfony 3.4 onwards.

I know what you're thinking though:

Sure, it's great to use user impersonation, but how do I do this with FOSUserBundle?

Well, the good news is the process is 99% the same.

But I get so many questions regarding FOSUserBundle integration with everything that I'm going to cover this preemptively.

Adding FOSUserBundle

As we will not be covering the setup of FOS User Bundle in this video, please begin by checking out the tag for the starting point to this video:

git checkout tags/vid-3-starting -b

I am going to assume you have at least followed the setup steps from the previous video. If you have not, please do so before continuing.

If you are directly following on from the previous video then please first re-run make dev, as there are new environment variables to be added which will not immediately import if simply changing branches.

Now get the environment up and running if you have not already done so:

make dev

Next, jump on to your running Docker PHP container:

docker-compose exec php /bin/bash

Once inside, install the new third party requirements:

composer install

And once the new dependencies are installed, it's time to update the database:

php bin/console doctrine:schema:update -f

Updating database schema...
Database schema updated successfully! "1" query was executed

Ok, so we could either add in new users manually, or we can skip that, and add in some users via fixtures.

I've provided some hand-rolled fixtures as I was experiencing problems with Doctrine Fixtures in Symfony 3.4. Anyway, let's use them:

(it goes without saying that you don't want to run this command in your live / real environments as it will drop your user table)

php bin/console crv:create-users

Starting up, beep, boop...
==========================

 [OK] Demo Application Setup Sequence Complete                                                                          

This command adds in the three users with the same credentials we had before:

  • admin
  • bob
  • dave

At this point we should have a working profile endpoint as before.

First we need to log in:

curl -X POST \
  http://127.0.0.1:81/app_dev.php/login \
  -H 'cache-control: no-cache' \
  -H 'content-type: application/json' \
  -d '{ "username": "admin", "password": "admin" }'

We get back a token:

{
    "token": "eyJhbGciOiJSUzI1NiJ9.eyJyb2xlcyI6WyJST0xFX0FETUlOIiwiUk9MRV9VU0VSIl0sInVzZXJuYW1lIjoiYWRtaW4iLCJpYXQiOjE1MDkxODkwNDEsImV4cCI6MTUwOTI3NTQ0MX0.oEZfqFCuaBp3inNLL8fyBIJ6fN_M0rP8vTJQycYNAivi_ufolM3nRxKjI8WO-Z_O9F46Qfoq2GnHhLfB3uON5ayRw-G5injGQbPisjlQFD_Rf5hPXQEgDUjkrjdply7yzEnPhXvk43b4IS6gB5l6b--SYaomtgQqptt8gyaCmu2NlUsBqf1WrhZaSjTCBRaQ9pU89EAC4-BbSMqXRspZfNDuVIYY_in2TxRlzXFia5KgQTSnLa_xprslpIuAFdeYLT-hJVAB7WXHLorhUQUM_UiLhGYjPQ30XBBHe5UMZhTDGFCE8I27JumAVEnTVhBnaB2XYq2JaT9DgVtHCWzQ2RJ_0W33fTU65A_ZEnxRWUf4XGJdPtaWL86CIwZXb79-XGxA53BrQ2hspMHLxeT_Q7LtlQxpwZK3EDdwFJvlRMDdsuIG_YHeSB1SpsDff90tpEXvORMGMISSSyPfE4QeDOvm84glEC-gSLDLqqbQ6QiE9RVsMSWCLQy-zMqUyxTPQ7sHehukkS_c1_T9r8d3sVfCHDhUADphECCpDHZ7COlx5zeY8M-PhjwDkAZzSxiXEMs0Eiaut_FMRuSv03RkCiDp_-Af6q1n89ri2qwFJ0fdJjfj5112J4pIFGEzAjwX0lcwHbv37TnoKD3jE_W_Lj2JR47t6b06vzY6_s5uAOU"
}

We use the token to make a request to our endpoint:

curl -X GET \
  http://127.0.0.1:81/app_dev.php/profile \
  -H 'authorization: Bearer eyJhbGciOiJSUzI1NiJ9.eyJyb2xlcyI6WyJST0xFX0FETUlOIiwiUk9MRV9VU0VSIl0sInVzZXJuYW1lIjoiYWRtaW4iLCJpYXQiOjE1MDkxODkwNDEsImV4cCI6MTUwOTI3NTQ0MX0.oEZfqFCuaBp3inNLL8fyBIJ6fN_M0rP8vTJQycYNAivi_ufolM3nRxKjI8WO-Z_O9F46Qfoq2GnHhLfB3uON5ayRw-G5injGQbPisjlQFD_Rf5hPXQEgDUjkrjdply7yzEnPhXvk43b4IS6gB5l6b--SYaomtgQqptt8gyaCmu2NlUsBqf1WrhZaSjTCBRaQ9pU89EAC4-BbSMqXRspZfNDuVIYY_in2TxRlzXFia5KgQTSnLa_xprslpIuAFdeYLT-hJVAB7WXHLorhUQUM_UiLhGYjPQ30XBBHe5UMZhTDGFCE8I27JumAVEnTVhBnaB2XYq2JaT9DgVtHCWzQ2RJ_0W33fTU65A_ZEnxRWUf4XGJdPtaWL86CIwZXb79-XGxA53BrQ2hspMHLxeT_Q7LtlQxpwZK3EDdwFJvlRMDdsuIG_YHeSB1SpsDff90tpEXvORMGMISSSyPfE4QeDOvm84glEC-gSLDLqqbQ6QiE9RVsMSWCLQy-zMqUyxTPQ7sHehukkS_c1_T9r8d3sVfCHDhUADphECCpDHZ7COlx5zeY8M-PhjwDkAZzSxiXEMs0Eiaut_FMRuSv03RkCiDp_-Af6q1n89ri2qwFJ0fdJjfj5112J4pIFGEzAjwX0lcwHbv37TnoKD3jE_W_Lj2JR47t6b06vzY6_s5uAOU' \
  -H 'cache-control: no-cache' \
  -H 'content-type: application/json'

And we see our profile:

{
    "id": 1,
    "username": "admin",
    "usernameCanonical": "admin",
    "salt": null,
    "email": "admin@example.com",
    "emailCanonical": "admin@example.com",
    "password": "$2y$13$K7w68iE9cNGszIgnPOjKoOSzlZMEVy0ZeDo0Q235cOgVuTeQO6uum",
    "plainPassword": null,
    "lastLogin": "2017-10-28T12:11:45+01:00",
    "confirmationToken": null,
    "roles": [
        "ROLE_ADMIN",
        "ROLE_USER"
    ],
    "accountNonExpired": true,
    "accountNonLocked": true,
    "credentialsNonExpired": true,
    "enabled": true,
    "superAdmin": false,
    "passwordRequestedAt": null,
    "groups": [],
    "groupNames": []
}

Again, we are exposing way too much private data here. This has been covered in a separate video, so won't be covered here again.

We still have our switch user setup enabled, so let's use it:

curl -X GET \
  http://127.0.0.1:81/app_dev.php/profile \
  -H 'authorization: Bearer eyJhbGciOiJSUzI1NiJ9.eyJyb2xlcyI6WyJST0xFX0FETUlOIiwiUk9MRV9VU0VSIl0sInVzZXJuYW1lIjoiYWRtaW4iLCJpYXQiOjE1MDkxODkwNDEsImV4cCI6MTUwOTI3NTQ0MX0.oEZfqFCuaBp3inNLL8fyBIJ6fN_M0rP8vTJQycYNAivi_ufolM3nRxKjI8WO-Z_O9F46Qfoq2GnHhLfB3uON5ayRw-G5injGQbPisjlQFD_Rf5hPXQEgDUjkrjdply7yzEnPhXvk43b4IS6gB5l6b--SYaomtgQqptt8gyaCmu2NlUsBqf1WrhZaSjTCBRaQ9pU89EAC4-BbSMqXRspZfNDuVIYY_in2TxRlzXFia5KgQTSnLa_xprslpIuAFdeYLT-hJVAB7WXHLorhUQUM_UiLhGYjPQ30XBBHe5UMZhTDGFCE8I27JumAVEnTVhBnaB2XYq2JaT9DgVtHCWzQ2RJ_0W33fTU65A_ZEnxRWUf4XGJdPtaWL86CIwZXb79-XGxA53BrQ2hspMHLxeT_Q7LtlQxpwZK3EDdwFJvlRMDdsuIG_YHeSB1SpsDff90tpEXvORMGMISSSyPfE4QeDOvm84glEC-gSLDLqqbQ6QiE9RVsMSWCLQy-zMqUyxTPQ7sHehukkS_c1_T9r8d3sVfCHDhUADphECCpDHZ7COlx5zeY8M-PhjwDkAZzSxiXEMs0Eiaut_FMRuSv03RkCiDp_-Af6q1n89ri2qwFJ0fdJjfj5112J4pIFGEzAjwX0lcwHbv37TnoKD3jE_W_Lj2JR47t6b06vzY6_s5uAOU' \
  -H 'cache-control: no-cache' \
  -H 'content-type: application/json' \
  -H 'x-switch-user: bob'

And... oh my!

{
    "error": {
        "code": 403,
        "message": "Forbidden",
        "exception": [
            ...

Wait... what? Why?

I tricked you. Ho ho.

In this setup, you must have ROLE_SUPER_ADMIN to impersonate users:

# app/config/security.yml

security:

    role_hierarchy:
        ROLE_ADMIN:                 ROLE_USER
        ROLE_SUPER_ADMIN:           [ROLE_ADMIN,ROLE_ALLOWED_TO_SWITCH]

How devious.

Ok, let's update our CreateUserCommand to give our admin user the super powers needed:

// src/AppBundle/Command/CreateUsersCommand.php

    private function createUsers()
    {
        $this->em->persist(
            $this
                ->userManager
                ->createUser()
                ->setUsername('admin')
                ->setEmail('admin@example.com')
                ->addRole('ROLE_SUPER_ADMIN')
                ->setPlainPassword('admin')
                ->setEnabled(true)
        );

And re-run the create users command:

php bin/console crv:create-users

Now when we send in our impersonation request once more, we see Bob's profile as expected:

{
    "id": 2,
    "username": "bob",
    "usernameCanonical": "bob",
    "salt": null,
    "email": "bob@example.com",
    "emailCanonical": "bob@example.com",
    "password": "$2y$13$mNmI7PqRJjBzFlHLyv6nvuqaGLtCnFqu.qbQpzuLbN5kBeqM4KHcG",
    "plainPassword": null,
    "lastLogin": null,
    "confirmationToken": null,
    "roles": [
        "ROLE_USER"
    ],
    "accountNonExpired": true,
    "accountNonLocked": true,
    "credentialsNonExpired": true,
    "enabled": true,
    "superAdmin": false,
    "passwordRequestedAt": null,
    "groups": [],
    "groupNames": []
}

It might be nicer if we changed up the endpoint to /profile/{userId}. That's what we will get onto in the next video.

Code For This Course

Get the code for this course.

Episodes

# Title Duration
1 Tutorial Setup - Getting Docker Up and Running 02:57
2 Impersonating Users on a JSON API (Without FOS User Bundle) 05:40
3 Impersonating Users on a JSON API (With FOS User Bundle) 03:40
4 Digging A Little Deeper 06:19