Symfony2: Security enhancements part I

Posted on by Matthias Noback

There's a much more detailed chapter about this subject in my book A Year With Symfony.

When working with Symfony2, you already have many of the finest tools for securing your web application. There are cases however that require you to add that extra bit. In this post I will point you to the right extension points within a Symfony2 project (or any other project which uses the Security Component for that matter).

Install NelmioSecurityBundle

See the README of the NelmioSecurityBundle. It contains many add-ons for your project, to sign/encrypt cookies, force SSL, prevent clickjacking and prevent untrusted redirects.

Log Security Information

When anything noteworthy related to security happens inside your application, log it to the "security" channel of the application logger. This way you can easily filter your logs in case of a security emergency. When injecting the logger into a service, do it like this:

class SecurityRelatedClass
{
    private $logger;

    public function __construct(LoggerInterface $logger = null)
    {
        // there may be no logger defined, depending on your site's configuration

        $this->logger = $logger;
    }

    public function doSomething()
    {
        if ($this->logger instanceof LoggerInterface) {
            // don't make it an error, since there is no failure of the system here

            $this->logger->info('Interesting security-related information');
        }

        // ...
    }
}

In the service definition add a tag and mention the channel:

<service id="matthias_security.security_related"
         class="Matthias\ApplicationBundle\SecurityRelatedClass">
    <argument type="service" id="logger" on-invalid="null" />
    <tag name="monolog.logger" channel="security" />
</service>

Enhance CSRF Protection

Each form by default has a "_token" field containing a token that is unique per user since it is based on the session ID. Usually it is the same for all forms of your application. But it is possible to make it different for each form. Use the "intention" option for form types:

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

class RegisterType extends AbstractType
{
    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        $resolver->setDefaults(array(
            'intention' => 'register'
        ));
    }
}

Use a Real Form Type for the Login Form

I don't know why the Symfony documentation tells you to circumvent the entire Form Component and just write your login form in plain and simple HTML. I think it is much better to create your own form type, register it as a service and then render your form in a Twig template, using {{ form_widget(form) }}, just like any other form. This will (almost) automatically give you the benefit of a standard CSRF token. To make it work, you will also have to adapt your login form type's options and the configuration for each "form_login" section in security.yml:

security:
    firewalls:
        my_firewall:
            pattern:    ^/
            form_login:
                username_parameter: "login[username]"
                password_parameter: "login[password]"
                csrf_parameter: "login[_token]"
                csrf_provider: form.csrf_provider
                intention: authentication

As you can see, we need to provide the name of the form fields containing the username, password and CSRF token. Make sure the intention in the form type matches the one in the security configuration:

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

class LoginType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('username', 'text')
            ->add('password', 'password');
    }

    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        $resolver->setDefaults(array(
            'intention' => 'authentication'
        ));
    }

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

In part II: session settings, session invalidation and keeping track of failed login attempts.

PHP Security Symfony2 authentication CSRF logging