schizoskmrkxx schizoskmrkxx - 5 months ago 89
Ajax Question

FOSUserBundle register in AngularJS

I have a SPA web application that uses

AngularJS
for the frontend and
Symfony2
for the backend.

I used
FOSUserBundle
for handling the
User
.

What I want to do right now is to use the
AngularJS
method of registering my
User
which is via
Ajax


My problem is that whenever I submit the form, it prints
"invalid form"
in the console log.

Here's my current progress:

new.html


<form class="form-group text-left" ng-submit="submit()" novalidate name="userFrm">
<div class="form-group">
<label for="user.email" class="required">Email</label>
<input id="user.email" name="user.email" class="form-control" type="text" ng-model="user.email" />
</div>
<div class="form-group">
<label for="user.username" class="required">Username</label>
<input id="user.username" name="user.username" class="form-control" type="text" ng-model="user.username" />
</div>
<div class="form-group">
<label for="user.plainPassword" class="required">Password</label>
<input id="user.plainPassword" name="user.plainPassword" class="form-control" type="password" ng-model="user.plainPassword" />
</div>
<div class="form-group">
<label for="confirmPassword" class="required">Confirm Password</label>
<input id="confirmPassword" name="confirmPassword" compare-to="user.plainPassword" class="form-control" type="password" ng-model="confirmPassword" />
</div>
<input type="submit" value="Register" ng-disabled="userFrm.$invalid" class="btn btn-primary center-block col-lg-2" />
</form>




new.js


'use strict';

(function () {
angular.module('myApp.user.new', [])
.config(['$stateProvider', function ($stateProvider) {
$stateProvider
.state('user.new', {
url: "/new",
controller: "NewUserCtrl",
templateUrl: PATH + 'user/new/new.html'
});
}])

.controller('NewUserCtrl', ["$scope", "$http", "$state", function ($scope, $http, $state) {

var success = function (response) {
var valid = response.data.valid;
if (valid) {
$state.go('home');
} else {
console.log("invalid form");
}
};

var error = function (reason) {
console.log("Submission failed");
};

$scope.submit = function () {
var formData = {
fos_user_registration: $scope.user,
confirmPass: $scope.confirmPassword
};

$http.post(Routing.generate('fos_user_registration_register'), $.param(formData), {
headers: {'Content-Type': 'application/x-www-form- urlencoded'}
})
.then(success, error);
};
}]);

}());


RegistrationController.php
(overridden from FOSUserBundle)

public function registerAction(Request $request) {
/** @var $formFactory \FOS\UserBundle\Form\Factory\FactoryInterface */
$formFactory = $this->get('fos_user.registration.form.factory');
/** @var $userManager \FOS\UserBundle\Model\UserManagerInterface */
$userManager = $this->get('fos_user.user_manager');

$user = $userManager->createUser();
$user->setEnabled(true);

$form = $formFactory->createForm();
$form->setData($user);

$form->handleRequest($request);

if ($form->isValid()) {
$user->addRole('ROLE_ADMIN');
$userManager->updateUser($user);

$response = ['valid' => true];
return new JsonResponse($response);
}


$response = ['valid' => false];
return new JsonResponse($response);
}

Answer

I don't see a CSRF token in your form. Your form may not be validated without CSRF token. Check here first; http://symfony.com/doc/current/cookbook/security/csrf_in_login_form.html

Also it may be better to generate your forms with twig templating engine for complete compatibility. See here; http://symfony.com/doc/current/book/forms.html

For further investigation why your form is not being validated, you can write an else block for $form->isValid() check and use the method in the answer to see your form errors. You can examine why your form is not being validated. http://stackoverflow.com/a/17428869/3399234

UPDATE

I come up with a solution. I used my Vagrant configuration which includes symfony 2.6.10. I have overridden the RegistrationFormType, place it in my own bundle and injected it as a service, just like FOS does. I replaced the FOS registration form with my own service alias. So I managed to switch off csrf protection in my overriden RegistrationFormType.

Also added to set plainPassword to user model to fix persistence error in USerManager.

The controller, overrides FOS registration controller.

<?php

namespace Acme\WebBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use FOS\UserBundle\Controller\RegistrationController as BaseController;
use Symfony\Component\HttpFoundation\Request as Request;
use Symfony\Component\HttpFoundation\JsonResponse as JsonResponse;

class RegistrationController extends BaseController
{
     public function registerAction()
        {

            $request = Request::createFromGlobals();

            $form = $this->container->get('fos_user.registration.form');
            /** @var $userManager \FOS\UserBundle\Model\UserManagerInterface */
            $userManager = $this->container->get('fos_user.user_manager');

            $user = $userManager->createUser();
            $form->setData($user);

            $form->handleRequest($request);

            if ($form->isValid()) {             
                $user->setEnabled(true);
                $user->addRole('ROLE_ADMIN');
                $userManager->updateUser($user);

                $response = ['valid' => true];
                return new JsonResponse($response);
            } else {
                $string = (string) $form->getErrors(true, false);
                //Show errors
                $response = ['valid' => false];
                return new JsonResponse($response);

            }
           return $this->container->get('templating')->renderResponse('AcmeWebBundle:Default:index.html.twig');
        }

}

Overriden FOS Registration form,

<?php
//Acme\WebBundle\Form\Type\RegistrationFormType.php
namespace Acme\WebBundle\Form\Type;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;

class RegistrationFormType extends AbstractType
{

    private $class;

    /**
     * @param string $class The User class name
     */
    public function __construct($class)
    {
        $this->class = $class;
    }

    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => $this->class,
            'intention'  => 'registration',
            'csrf_protection' => false, //this line does the trick ;)
        ));
    }

    public function getParent()
    {
        return 'fos_user_registration';
    }

    public function getName()
    {
        return 'acme_user_registration';
    }
}

Services.yml

services:
    acme.registration.form.type:
        class: Acme\WebBundle\Form\Type\RegistrationFormType
        arguments: ["%fos_user.model.user.class%"]
        tags:
            - { name: form.type, alias: acme_user_registration }

index.html.twig

     <script type="text/javascript">

                       var app = angular.module('myApp',  []);

                            app.controller('NewUserCtrl', ["$scope", "$http", function ($scope, $http) {

                                    var success = function (response) {
                                        var valid = response.data.valid;
                                        if (valid) {
                                            $state.go('home');
                                        } else {
                                            console.log("invalid form");
                                        }
                                    };

                                    var error = function (reason) {
                                        console.log("Submission failed");
                                    };

                                    $scope.submit = function () {
                                        var formData = {
                                            fos_user_registration_form: $scope.user
                                        };

                                    $http.post('<YOUR URL HERE>', $.param(formData), {
                                            headers: {'Content-Type': 'application/x-www-form-urlencoded'}
                                        }).then(success, error);
                                    };
                                }]);
                </script>

<div id="content" ng-app="myApp" ng-controller="NewUserCtrl" >

    <form class="form-group text-left" ng-submit="submit()" novalidate name="userFrm">
        <div class="form-group">
            <label for="user.email" class="required">Email</label>
            <input id="user.email" name="user.email" class="form-control" type="text" ng-model="user.email" />
        </div>
        <div class="form-group">
            <label for="user.username" class="required">Username</label>
            <input id="user.username" name="user.username" class="form-control" type="text" ng-model="user.username" />
        </div>
        <div class="form-group">
            <label for="user.plainPassword.first" class="required">Password</label>
            <input id="user.plainPassword.first" name="user.plainPassword.first" class="form-control" type="password" ng-model="user.plainPassword.first" />
        </div>
        <div class="form-group">
            <label for="user.plainPassword.second" class="required">Confirm Password</label>
            <input id="user.plainPassword.second" name="user.plainPassword.second" compare-to="user.plainPassword.first" class="form-control" type="password" ng-model="user.plainPassword.second" />
        </div>
        <input type="submit" value="Register" ng-disabled="userFrm.$invalid"  class="btn btn-primary center-block col-lg-2" />
    </form>
</div>

This is the fos_user configuration in config.yml to change default form with your overridden form whenever FOS User bundle's registration form is summoned.

config.yml

fos_user:
    registration:
        form:
             type: acme_user_registration

And that's it I can post with the form and persist the user to database then return the {"valid":true} response as expected. And finally i have chance to learn how to inject AngularJS to Symfony 2, cheers for that.