Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine
This tutorial has a new version, check it out!

PUT Validation and CSRF Tokens

Video not working?

It looks like your browser may not support the H264 codec. If you're using Linux, try a different browser or try installing the gstreamer0.10-ffmpeg gstreamer0.10-plugins-good packages.

Thanks! This saves us from needing to use Flash or encode videos in multiple formats. And that let's us get back to making more videos :). But as always, please feel free to message us.

Validation for newAction(), check! Now let's repeat for updateAction. And that's not much work - we just need to add the whole if (!$form->isValid()) block. I know you hate duplication, so copy the inside of that if statement, head to the bottom of the class, and add a new private function createValidationErrorResponse(). We'll pass it the $form object, and we should type-hint that argument with FormInterface because we're good programmers! Paste the stuff here:

... lines 1 - 10
use Symfony\Component\Form\FormInterface;
... lines 12 - 15
class ProgrammerController extends BaseController
{
... lines 18 - 165
private function createValidationErrorResponse(FormInterface $form)
{
$errors = $this->getErrorsFromForm($form);
$data = [
'type' => 'validation_error',
'title' => 'There was a validation error',
'errors' => $errors
];
return new JsonResponse($data, 400);
}
}

Cool! Any time we have a form, we can pass it here and get back a perfectly consistent validation error response. Go back up to newAction() and use this: return $this->createValidationErrorResponse() and pass it the $form object:

... lines 1 - 15
class ProgrammerController extends BaseController
{
... lines 18 - 21
public function newAction(Request $request)
{
... lines 24 - 27
if (!$form->isValid()) {
return $this->createValidationErrorResponse($form);
}
... lines 31 - 45
}
... lines 47 - 177
}

Copy those three lines and repeat in updateAction():

... lines 1 - 15
class ProgrammerController extends BaseController
{
... lines 18 - 88
public function updateAction($nickname, Request $request)
{
... lines 91 - 101
$form = $this->createForm(new UpdateProgrammerType(), $programmer);
$this->processForm($request, $form);
if (!$form->isValid()) {
return $this->createValidationErrorResponse($form);
}
... lines 108 - 115
}
... lines 117 - 177
}

We could write a test for this, but we've centralized everything so well, that I'm confident that if it works in newAction, it works in updateAction(). Basically, I think that's overkill. But we should re-run our test:

bin/phpunit -c app --filter testValidationErrors

All good. Now run all the tests:

bin/phpunit -c app

Oh! They break immediately! The POST is failing with a 400 response: invalid CSRF token - we saw this a few minutes ago. Every endpoint is failing because we're never sending a CSRF token.

Symfony forms always expect a token. But because we're building a stateless, or session-less API, we don't need CSRF tokens. You would need it if you have a JavaScript frontend that's relying on cookies to authenticate, but you don't need it if your API doesn't store the user in the session.

Let's turn it off. Inside ProgrammerType, in setDefaultOptions() - or configureOptions() if you're on a newer version of Symfony - set csrf_protection to false:

... lines 1 - 8
class ProgrammerType extends AbstractType
{
... lines 11 - 34
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
... lines 38 - 39
'csrf_protection' => false,
));
}
... lines 43 - 47
}

That'll do it! Try the tests:

bin/phpunit -c app

Back to green! If you're using your form types for HTML pages and on your API, you won't want to set csrf_protection to false inside the class - that'll remove it everywhere. Instead, you can pass csrf_protection in as an option in the third argument to createForm() in your controller. Or you can do something fancier like a Form Type Extension and control this option on a global basis.

FOSRestBundle has an interesting version of this. In the View Layer part of their docs, they show a configuration option that disables CSRF protection based on a role the user has. The idea is that only users that are authenticated via the sessionless-API would have the role you put here. That's a cool idea.

Leave a comment!

2
Login or Register to join the conversation
Amy anuszewski Avatar
Amy anuszewski Avatar Amy anuszewski | posted 5 years ago

Never mind. I'm an idiot.

Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

This tutorial uses an older version of Symfony. The concepts of REST and errors are still valid, but I recommend using API Platform in new Symfony apps.

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": ">=5.3.3",
        "symfony/symfony": "2.6.*", // v2.6.11
        "doctrine/orm": "~2.2,>=2.2.3,<2.5", // v2.4.7
        "doctrine/dbal": "<2.5", // v2.4.4
        "doctrine/doctrine-bundle": "~1.2", // v1.4.0
        "twig/extensions": "~1.0", // v1.2.0
        "symfony/assetic-bundle": "~2.3", // v2.6.1
        "symfony/swiftmailer-bundle": "~2.3", // v2.3.8
        "symfony/monolog-bundle": "~2.4", // v2.7.1
        "sensio/distribution-bundle": "~3.0,>=3.0.12", // v3.0.21
        "sensio/framework-extra-bundle": "~3.0,>=3.0.2", // v3.0.7
        "incenteev/composer-parameter-handler": "~2.0", // v2.1.0
        "hautelook/alice-bundle": "0.2.*", // 0.2
        "jms/serializer-bundle": "0.13.*" // 0.13.0
    },
    "require-dev": {
        "sensio/generator-bundle": "~2.3", // v2.5.3
        "behat/behat": "~3.0", // v3.0.15
        "behat/mink-extension": "~2.0.1", // v2.0.1
        "behat/mink-goutte-driver": "~1.1.0", // v1.1.0
        "behat/mink-selenium2-driver": "~1.2.0", // v1.2.0
        "phpunit/phpunit": "~4.6.0" // 4.6.4
    }
}
userVoice