1

I'm building an OAuth2 server with the great FOSOAuthServerBundle.

My Symfony app authentifies users coming from another source (actually another server).

I have created a custom UserProvider which works fine with the default Symfony security layer. I have my implementation of UserInterface but it's obviously not an Entity of the database.

Implementing the classes for AccessToken, RefreshToken & AuthCode as told in the installation instructions for Doctrine, I defined the user reference as

//...
/**
 * @ORM\Column(type="integer")
 */
protected $user_id;
//...

Does it seem logical until there ?

Now the true question is: Where do I specify the way to link my users to the different tokens ?


I've also been asking the question on the official repo for visibility purpose

Pierre de LESPINAY
  • 44,700
  • 57
  • 210
  • 307

1 Answers1

1

I ended up doing 2 things:

  • Overriding the tokens setter & getter for the property user
  • Using a Doctrine LifecycleEvent listener

First extend the token classes (for example with a Trait)

namespace AppBundle\Entity;

use AppBundle\Security\User;
use Symfony\Component\Security\Core\User\UserInterface;

trait AppTokenTrait
{
    /**
     * @ORM\Column(type="string")
     * @var string
     */
    protected $username;

    public function setUser(UserInterface $user)
    {
        if (!$user instanceof User) {
            throw new \InvalidArgumentException(
                sprintf("User must be an instance of %s", User::class)
            );
        }

        $this->username = $user->getUsername();
        $this->user = $user;
    }

    public function getUser()
    {
        if (!$this->user) {
            throw new \RuntimeException(
                "Unable to get user - user was not loaded by postLoad"
            );
        }

        return $this->user;
    }

    /**
     * @return string
     */
    public function getUsername()
    {
        return $this->username;
    }
}

In the 3 token classes (And don't forget to update the database schema):

//...
    use AppTokenTrait;
//...

Create the listener

namespace AppBundle\Services;

use AppBundle\Entity\AccessToken;
use AppBundle\Entity\AuthCode;
use AppBundle\Entity\RefreshToken;
use AppBundle\Security\UserProvider;
use Doctrine\ORM\Event\LifecycleEventArgs;

class AppTokenListener
{
    /**
     * @var UserProvider
     */
    protected $userProvider;

    public function __construct(UserProvider $userProvider)
    {
        $this->userProvider = $userProvider;
    }

    public function postLoad(LifecycleEventArgs $event)
    {
        $object = $event->getObject();

        if (
            $object instanceof AccessToken or
            $object instanceof AuthCode or
            $object instanceof RefreshToken
        ) {
            $user = $this->userProvider->loadUserByUsername($object->getUsername());
            $object->setUser($user);
        }
    }
}

Register it as a service

app.token.listener:
    class: AppBundle\Services\AppTokenListener
    tags:
        - { name: doctrine.event_listener, event: postLoad }

The user object should now always be attached to the tokens

Pierre de LESPINAY
  • 44,700
  • 57
  • 210
  • 307
  • Hi Pierre, your post really helped me setting up a similar configuration. I have everything up and running from the oauth side, but calling my api with the access token leads in a fatal error: "Error: Call to a member function loadUserByUsername() on a non-object" at ***/OAuthBundle/Services/SecurityTokenListener.php line 41, where line 41 is $user = $this->userProvider->loadUserByUsername($object->getUsername());" It seems, that the constructor is never called. Any idea, what's going on here? Thanks, Chris – Chris Aug 28 '15 at 15:02
  • All I remember having here is a circular reference issue when injecting the `userProvider` into the listener, and I saw this was a known issue that I workedaround in a quite dirty way – Pierre de LESPINAY Aug 28 '15 at 15:50
  • Got it now, the service just needs the user provider as an argument and your AppTokenListener is missing a "_" at the constructor. Copy/Paste isn't a good idea:-) – Chris Aug 29 '15 at 08:19
  • How did you get past this circular reference thing? – Benoit Duffez Jul 30 '17 at 21:46