Search code examples
phprestsymfonycsrf

FOSUserBundle & REST Api Call: How to use custom FormType?


I am using FOSUserBundle on my Symfony2 Website. Now I am working on an API to allow registration over a REST api call.

I have overridden the RegistrationController of FOSUserBundle:

ApiRegistrationController.php:

 /**
 * @Route("/user/", defaults = { "_format" = "json" }, requirements = { "_method" = "POST" })
 * 
 */
public function registerAction(Request $request)
{
    [...]

    $form = $formFactory->createForm(new ApiRegistrationFormType(), $user);

    [...]
}

ApiRegistrationFormType.php:

/**
 * @param OptionsResolverInterface $resolver
 */
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
    $resolver->setDefaults(array(
        'data_class' => 'BenMarten\UserBundle\Entity\User',
        'intention'  => 'registration',
        'csrf_protection' => false
    ));
}

I get the error about the wrong CSRF token, so I created my own RegistrationFormType, to disable CSRF - but it is into being called...

How can I achieve disabling the CSRF token only for the api calls?


Solution

  • So after a lot of time fiddling around, I got it working. I hope it saves someone some time.

    I use FOSRestBundle.

    I have created my own RestController in my bundle:

    RestController.php

    namespace BenMarten\UserBundle\Controller;
    
    use Symfony\Bundle\FrameworkBundle\Controller\Controller;
    use FOS\UserBundle\FOSUserEvents;
    use FOS\UserBundle\Event\FormEvent;
    use FOS\UserBundle\Event\GetResponseUserEvent;
    use Symfony\Component\HttpFoundation\Request;
    use Symfony\Component\HttpFoundation\Response;
    use FOS\RestBundle\View\View;
    
    /**
     * Rest Controller
     */
    class RestController extends Controller
    {
        /**
         * Create a new resource
         *
         * @param Request $request
         * @return View view instance
         *
         */
        public function postUserAction(Request $request)
        {
            /** @var $formFactory \FOS\UserBundle\Form\Factory\FactoryInterface */
            $formFactory = $this->container->get('fos_user.registration.form.factory');
            /** @var $userManager \FOS\UserBundle\Model\UserManagerInterface */
            $userManager = $this->container->get('fos_user.user_manager');
            /** @var $dispatcher \Symfony\Component\EventDispatcher\EventDispatcherInterface */
            $dispatcher = $this->container->get('event_dispatcher');
    
            $user = $userManager->createUser();
            $user->setEnabled(true);
    
            $event = new GetResponseUserEvent($user, $request);
            $dispatcher->dispatch(FOSUserEvents::REGISTRATION_INITIALIZE, $event);
    
            if (null !== $event->getResponse()) {
                return $event->getResponse();
            }
    
            $form = $formFactory->createForm();
            $form->setData($user);
    
            $jsonData = json_decode($request->getContent(), true); // "true" to get an associative array
    
            if ('POST' === $request->getMethod()) {
                $form->bind($jsonData);
    
                if ($form->isValid()) {
                    $event = new FormEvent($form, $request);
                    $dispatcher->dispatch(FOSUserEvents::REGISTRATION_SUCCESS, $event);
    
                    $userManager->updateUser($user);
    
                    $response = new Response("User created.", 201);               
    
                    return $response;
                }
            }
    
            $view = View::create($form, 400);
            return $this->get('fos_rest.view_handler')->handle($view);
        }
    }
    

    RestRegistrationFormType.php

    namespace BenMarten\UserBundle\Form;
    
    use Symfony\Component\Form\AbstractType;
    use Symfony\Component\Form\FormBuilderInterface;
    use Symfony\Component\OptionsResolver\OptionsResolverInterface;
    use Symfony\Component\DependencyInjection\Container;
    
    class RestRegistrationFormType extends AbstractType
    {
        protected $routeName;
        private $class;
    
        /**
         * @param string $class The User class name
         */
        public function __construct(Container $container, $class)
        {
            $request = $container->get('request');
            $this->routeName = $request->get('_route');
            $this->class = $class;
        }
    
        public function buildForm(FormBuilderInterface $builder, array $options)
        {
            if ($this->routeName != "post_user")
            {
                $builder
                    ->add('email', 'email', array('label' => 'form.email', 'translation_domain' => 'FOSUserBundle'))
                    ->add('username', null, array('label' => 'form.username', 'translation_domain' => 'FOSUserBundle'))
                    ->add('plainPassword', 'repeated', array(
                        'type' => 'password',
                        'options' => array('translation_domain' => 'FOSUserBundle'),
                        'first_options' => array('label' => 'form.password'),
                        'second_options' => array('label' => 'form.password_confirmation'),
                        'invalid_message' => 'fos_user.password.mismatch',
                    ))
                ;
            }
            else
            {
                $builder
                    ->add('email', 'email')
                    ->add('username', null)
                    ->add('plainPassword', 'password')
                ;        
            }
        }
    
        public function setDefaultOptions(OptionsResolverInterface $resolver)
        {
            if ($this->routeName != "post_user")
            {
                $resolver->setDefaults(array(
                    'data_class' => $this->class,
                    'intention'  => 'registration',
                ));
            }
            else
            {
                $resolver->setDefaults(array(
                    'data_class' => $this->class,
                    'intention'  => 'registration',
                    'csrf_protection' => false
                ));            
            }
        }
    
        public function getName()
        {
            return 'fos_user_rest_registration';
        }
    }
    

    Added the service to Services.xml:

    <service id="ben_marten.rest_registration.form.type" class="BenMarten\UserBundle\Form\RestRegistrationFormType">
            <tag name="form.type" alias="fos_user_rest_registration" />
            <argument type="service" id="service_container" />
            <argument>BenMarten\UserBundle\Entity\User</argument>
        </service>
    

    And in config.yml:

    registration:
        form:
            type: fos_user_rest_registration
    

    So basically I tell the FOS user bundle to use my own registration form and depending on the route i enable the csrf token or not...