Search code examples
symfonyrestful-authenticationfosrestbundlefosfacebookbundle

Symfony | FOSRestBundle with FOSFacebookBundle


I have a symfony app which serve a RESTful API(for mobile app) and have backend administration.

I can succesfuly login to the backend via facebook, but how should I allow loggin via the RESTful API?


Solution

  • Wooh.. after almost 12 hours(!) here is the solution for anyone who looking for too:

    1. We need to create new custom firewall
    2. This factory should connect to the FOSFacebook and validate the token
    3. If it using our new firewall it should manually disable any session or cookie.
    4. To use the firewall we need to send our token in every request

    The code

    • First define our firewall listener

    GoDisco/UserBundle/Security/Firewall/ApiFacebookListener.php

    <?php
    /**
     * Authored by  AlmogBaku
     *              [email protected]
     *              http://www.almogbaku.com/
     * 
     * 9/6/13 2:17 PM
     */
    
    namespace Godisco\UserBundle\Security\Firewall;
    
    use FOS\FacebookBundle\Security\Authentication\Token\FacebookUserToken;
    use Symfony\Component\HttpFoundation\Response;
    use Symfony\Component\HttpKernel\Event\GetResponseEvent;
    use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;
    use Symfony\Component\Security\Core\Exception\AuthenticationException;
    use Symfony\Component\Security\Core\SecurityContextInterface;
    use Symfony\Component\Security\Http\Firewall\ListenerInterface;
    use Symfony\Component\HttpFoundation\Session\Session;
    
    /**
     * API gateway through Facebook oAuth token: Firewall
     *
     * Class ApiFacebookListener
     * @package Godisco\UserBundle\Security\Firewall
     */
    class ApiFacebookListener implements ListenerInterface
    {
        /**
         * @var \Symfony\Component\Security\Core\SecurityContextInterface
         */
        protected $securityContext;
    
        /**
         * @var \Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface
         */
        protected $authenticationManager;
    
        /**
         * @var Session
         */
        protected $session;
    
        /**
         * @var string
         */
        protected $providerKey;
    
        public function __construct(SecurityContextInterface $securityContext, AuthenticationManagerInterface $authenticationManager, Session $session, $providerKey)
        {
            if (empty($providerKey)) {
                throw new \InvalidArgumentException('$providerKey must not be empty.');
            }
    
            $this->securityContext = $securityContext;
            $this->authenticationManager = $authenticationManager;
            $this->session = $session;
            $this->providerKey=$providerKey;
        }
    
        /**
         * @param \Symfony\Component\HttpKernel\Event\GetResponseEvent $event The event.
         */
        public function handle(GetResponseEvent $event)
        {
            $accessToken    = $event->getRequest()->get('access_token');
            $token          = new FacebookUserToken($this->providerKey, '', array(), $accessToken);
    
            /**
             * force always sending token
             */
            $_COOKIE=array();
            $this->session->clear();
    
    
            try {
                if($accessToken)
                    $returnValue = $this->authenticationManager->authenticate($token);
                    $this->securityContext->setToken($returnValue);
                }
            } catch(AuthenticationException $exception) {
                if(!empty($accessToken))
                    $event->setResponse(new Response(array("error"=>$exception->getMessage()),401));
            }
        }
    }
    
    • Than create a new security factory which calling our listener, and will connect the authentication to the FOSFacebookBundle.

    GoDisco/UserBundle/DependencyInjection/Security/Factory/ApiFacebookFactory.php

    <?php
    /**
     * Authored by  AlmogBaku
     *              [email protected]
     *              http://www.almogbaku.com/
     * 
     * 9/6/13 2:31 PM
     */
    
    namespace GoDisco\UserBundle\DependencyInjection\Security\Factory;
    
    use FOS\FacebookBundle\DependencyInjection\Security\Factory\FacebookFactory;
    use Symfony\Component\Config\Definition\Builder\NodeDefinition;
    use Symfony\Component\DependencyInjection\ContainerBuilder;
    use Symfony\Component\DependencyInjection\DefinitionDecorator;
    
    /**
     * API gateway through Facebook oAuth token: Factory
     *
     * Class ApiFacebookFactory
     * @package GoDisco\UserBundle\DependencyInjection\Security\Factory
     */
    class ApiFacebookFactory extends FacebookFactory
    {
        /**
         * {@inheritdoc}
         */
        public function getKey()
        {
            return 'api_facebook';
        }
    
        /**
         * {@inheritdoc}
         */
        public function addConfiguration(NodeDefinition $node)
        {
            $builder = $node->children();
            $builder
                ->scalarNode('provider')->end()
                ->booleanNode('remember_me')->defaultFalse()->end()
            ;
    
            foreach ($this->options as $name => $default) {
                if (is_bool($default)) {
                    $builder->booleanNode($name)->defaultValue($default);
                } else {
                    $builder->scalarNode($name)->defaultValue($default);
                }
            }
        }
    
        /**
         * {@inheritdoc}
         */
        protected function createEntryPoint($container, $id, $config, $defaultEntryPointId)
        {
            return null;
        }
    
        /**
         * {@inheritdoc}
         */
        protected function createListener($container, $id, $config, $userProvider)
        {
            $listenerId = "api_facebook.security.authentication.listener";
            $listener = new DefinitionDecorator($listenerId);
            $listener->replaceArgument(3, $id);
    
            $listenerId .= '.'.$id;
            $container->setDefinition($listenerId, $listener);
    
            return $listenerId;
        }
    }
    
    • Defining the listener service, so we can inject the arguments

    GoDisco/UserBundle/Resources/config/services.yml

    services:
        api_facebook.security.authentication.listener:
            class: GoDisco\UserBundle\Security\Firewall\ApiFacebookListener
            arguments: ['@security.context', '@security.authentication.manager', '@session', '']
    
    • Defining our new firewall!

    app/config/security.yml

    security:
            api:
                pattern: ^/api
                api_facebook:
                    provider: godisco_facebook_provider
                stateless:  true
                anonymous: true
            main:
                ...