Question

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?

Was it helpful?

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
 *              almog.baku@gmail.com
 *              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
 *              almog.baku@gmail.com
 *              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:
            ...

OTHER TIPS

You need to implement oAuth authentication from your client app.

This was answered before:

How to restfully login, Symfony2 Security, FOSUserBundle, FOSRestBundle?

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top