Search code examples
phpsymfonyoauthoauth-2.0fosoauthserverbundle

Symfony FOS OAuth with custom User


How is it possible to implement an OAuth server based on FOSOAuthServerBundle without using FOSUserBundle?

My user class starts like his:

<?php

namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\UserInterface;

/**
*
* @ORM\Entity
* @ORM\Table(name="user")
* @ORM\Entity(repositoryClass="AppBundle\Entity\UserRepository")
*/
class User implements UserInterface

My user repository class starts with this:

<?php

namespace AppBundle\Entity;

use Symfony\Bridge\Doctrine\Security\User\UserLoaderInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\NoResultException;

class UserRepository extends EntityRepository implements UserLoaderInterface, UserProviderInterface

In my security.yml I included the following:

    providers:
        user_db:
            entity:
                class: AppBundle\Entity\User
                property: username

And finally the config.yml:

fos_oauth_server:
    db_driver: orm
    client_class: AppBundle\Entity\OAuthClient
    access_token_class: AppBundle\Entity\OAuthAccessToken
    refresh_token_class: AppBundle\Entity\OAuthRefreshToken
    auth_code_class: AppBundle\Entity\OAuthAuthCode
    service:
        options:
            access_token_lifetime: 3600

        user_provider: user_db
        #user_provider: AppBundle\Entity\UserRepository
        #user_provider: AppBundle\Entity\User

Right now it throws an exception:

[1] Symfony\Component\Debug\Exception\FatalThrowableError: Call to a member function loadUserByUsername() on null
    at n/a
        in /home/wanderson/api/vendor/friendsofsymfony/oauth-server-bundle/Storage/OAuthStorage.php line 161

    at FOS\OAuthServerBundle\Storage\OAuthStorage->checkUserCredentials(object(OAuthClient), 'admin', 'admin')
        in /home/wanderson/api/vendor/friendsofsymfony/oauth2-php/lib/OAuth2.php line 929

    at OAuth2\OAuth2->grantAccessTokenUserCredentials(object(OAuthClient), array('grant_type' => 'password', 'scope' => null, 'code' => null, 'redirect_uri' => null, 'username' => 'admin', 'password' => 'admin', 'refresh_token' => null))
        in /home/wanderson/api/vendor/friendsofsymfony/oauth2-php/lib/OAuth2.php line 815

    at OAuth2\OAuth2->grantAccessToken(object(Request))
        in /home/wanderson/api/vendor/friendsofsymfony/oauth-server-bundle/Controller/TokenController.php line 42

    at FOS\OAuthServerBundle\Controller\TokenController->tokenAction(object(Request))
        in  line 

    at call_user_func_array(array(object(TokenController), 'tokenAction'), array(object(Request)))
        in /home/wanderson/api/vendor/symfony/symfony/src/Symfony/Component/HttpKernel/HttpKernel.php line 153

    at Symfony\Component\HttpKernel\HttpKernel->handleRaw(object(Request), '1')
        in /home/wanderson/api/vendor/symfony/symfony/src/Symfony/Component/HttpKernel/HttpKernel.php line 68

    at Symfony\Component\HttpKernel\HttpKernel->handle(object(Request), '1', true)
        in /home/wanderson/api/vendor/symfony/symfony/src/Symfony/Component/HttpKernel/Kernel.php line 169

    at Symfony\Component\HttpKernel\Kernel->handle(object(Request))
        in /home/wanderson/api/web/app_dev.php line 30

    at require('/home/wanderson/api/web/app_dev.php')
        in /home/wanderson/api/vendor/symfony/symfony/src/Symfony/Bundle/FrameworkBundle/Resources/config/router_dev.php line 40

Solution

  • Create a user provider, or if you want to use your repository class for it read 'how define repository like service' (i don't know working if it at newest version or not):

    https://stackoverflow.com/a/17230333/6848076 https://stackoverflow.com/a/31807608/6848076

    But i not recommended this way

    <?php 
    
    namespace AppBundle\Security\Provider\UserProvider;
    
    use Doctrine\Common\Persistence\ObjectRepository;
    use Doctrine\ORM\NoResultException;
    use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
    use Symfony\Component\Security\Core\User\UserInterface;
    use Symfony\Component\Security\Core\User\UserProviderInterface;
    use Symfony\Component\Serializer\Exception\UnsupportedException;
    use Doctrine\ORM\EntityManager;
    
    class UserProvider implements UserProviderInterface
    {
        protected $class;
    
        protected $userRepository;
    
        public function __construct(EntityManager $entityManager, $class)
        {
            $this->class = $class;
            $this->userRepository = $entityManager->getRepository($class);
        }
    
        public function loadUserByUsername($username)
        {
            $user = $this->userRepository->findOneBy(array('username' => $username));
            if (null === $user) {
                $message = sprintf(
                    'Unable to find an active User object identified by "%s"',
                    $username
                );
                throw new UsernameNotFoundException($message, 0, $failed);
            }
            return $user;
        }
    
        public function refreshUser(UserInterface $user)
        {
            $class = get_class($user);
            if (false == $this->supportsClass($class)) {
                throw new UnsupportedException(
                    sprintf(
                        'Instances of "%s" are not supported',
                        $class
                    )
                );
            }
            return $this->userRepository->find($user->getId());
        }
    
        public function supportsClass($class)
        {
            return $this->class === $class
                || is_subclass_of($class, $this->class);
    
        }
    }
    

    About services you can read: http://symfony.com/doc/current/service_container.html Define a provider service at {project_directory}/src/AppBundle/Resources/config/service.yml

    parameters:
        user.class: AppBundle\Entity\User
        user.provider.class: AppBundle\Security\Provider\UserProvider
    services:
        user.provider:
          class: %user.provider.class%
          arguments: [@doctrine.orm.entity_manager, %user.class%]
    

    Change fosoauth configuration at config.yml:

    fos_oauth_server:
        db_driver: orm
        client_class: AppBundle\Entity\OAuthClient
        access_token_class: AppBundle\Entity\OAuthAccessToken
        refresh_token_class: AppBundle\Entity\OAuthRefreshToken
        auth_code_class: AppBundle\Entity\OAuthAuthCode
        service:
            options:
                access_token_lifetime: 3600
            user_provider: user.provider
    

    About security.yml you can read: http://symfony.com/doc/current/security.html