Securing your web application with Symfony

Introduction

As a web developer, securing your web applications is one of the most important and complex tasks you’ll regularly undertake. You may need to use some security mechanism to make sure the visitor has valid credentials (authentication) within the domain of your application. You may additionally need to check to make sure that the individual user has permissions that enable them to perform certain actions within your application (authorisation).

Implementing this functionality can be complex because it typically touches many different parts of your application. You may have “public” areas with no authentication required but then other sections of the application that do require authentication. Additionally, some parts of the application may require authentication and then also require that the user account have some special permission in order to access or perform it.

Symfony 2 is designed with security in mind. It comes with a powerful authentication and authorisation framework out-of-the-box – all we need to do is to set it up and configure it for our particular use-case.

The Process

When using Symfony’s security framework, we apply authentication and authorisation rules based on URL patterns. The sequence of events is as follows:

  1. The user tries to access a URL that is “secured.”

  2. The Symfony security framework triggers the authentication scheme. Based on the settings this may be form-based log-in, a HTTP basic dialog prompt, OAuth via Facebook / Twitter / etc. or some other method.

  3. Once the credentials are submitted, the security framework tries to authenticate the details against a repository of users. This repository may be in the form of a hardcoded configuration file, a database or a third-party service (again, depending on the configuration).

  4. If authentication passes, the request is re-tried with the identity of the user in-place. Note that the user may still be denied access at this point if we’re using role-based access control.

We configure security settings for different parts of our application by setting up firewalls.

Firewalls

Firewalls are sets of rules that we can create to tell the Symfony security system how certain parts of the application should be treated in terms of authentication / authorisation. Each firewall has a pattern associated with it, which is a regular expression. If the current URL for a request matches the pattern for a firewall, the rules for that firewall are applied. An example of a firewall is provided in the “security.yml” file provided below:

# app/config/security.yml
# ...
security:
    firewalls:
        firewalled_area:
            pattern:    ^/
            anonymous: ~
            http_basic:
                realm: "Name of the realm"
# ...

Just because a URL matches a firewall doesn’t mean all URLs that match the pattern are secured. In the example above, anonymous access is allowed because of the modifier: anonymous: ~. If we want all users to be authenticated before accessing the firewalled area, we should omit this line.

For the sake of illustration in this example, we want to allow anonymous access to the top level URLs, but require authorisation for URLs under the “/admin/” folder. We do this by specifying the URL(s) that should be secured using an access control clause, as demonstrated below:

# app/config/security.yml
security:
    firewalls:
    # ...

    access_control:
        - { path: ^/admin, roles: ROLE_ADMIN }
# ...

These settings mean that only users that are authorised with the role “ROLE ADMIN” will be able to access URLs under “/admin/”.

Roles

In Symfony a “role” is a string token that represents a set of permissions for a particular user group. We can define arbitrary roles and then use them in our application. For example:

# app/config/security.yml
security:
    firewalls:
        firewalled_area:
            pattern:    ^/
            anonymous: ~
            http_basic:
                realm: "Name of the realm"


    access_control:
        - { path: ^/admin, roles: ROLE_GRANDPOOBAR }
# ...

In the example above, URLs under “/admin” will only be accessible by those with the role “ROLE GRANDPOOBAR.” We can even define hierarchical role relationships in our “security.yml” file using the following syntax:

# app/config/security.yml
security:
    role_hierarchy:
        ROLE_ADMIN:       ROLE_USER
        ROLE_GRANDPOOBAR: ROLE_ADMIN
        ROLE_GOD: [ROLE_GRANDPOOBAR, ROLE_SUPERUSER]
#...

In this example, giving someone the role of ROLE ADMIN means they also can access any actions that require the ROLE USER. Also note that users who have ROLE GRANDPOOBAR can do everything that those with ROLE ADMIN can, which (because of the role hierarchy) includes everything ROLE USER can do. We can also specify multiple roles in the hierarchy using the array syntax shown above for ROLE GOD.

User Providers

There is, however, one important thing we haven’t discussed yet. How do we actually specify where the Symfony security framework will pull user credentials from? This is where “User Providers” come in. A User Provider is a class that implements the UserProviderInterface interface, which includes functions for interacting with a repository of Users.

Out of the box, Symfony provides a default “in_memory” User Provider which enables us to hard-code credentials into the configuration files. We can use this method using the following syntax in “security.yml”:

# app/config/security.yml
# ...
    providers:
        in_memory:
            users:
                thomas: { password: tomspass }
                ryan:  { password: secret, roles: 'ROLE_USER' }
                admin: { password: topsecret, roles: 'ROLE_ADMIN' }
# ...

This particular example creates three users, thomas, ryan and admin, two of which have roles applied.

Password encoding

If you’ve developed a security system for a web application before, the code snippet above should worry you slightly. One of the cardinal rules of web security is that passwords should not appear in plain text – ever. Symfony’s security framework has you covered here as well, as it enables you to use “encoders” to transform plain text passwords into hashes.

If you haven’t used hashing before, the core idea is that we use a “hashing function” to encode a plain-text string into a random-looking sequence of characters. We store the password in this “hashed” format so that if an attacker does gain access to our database, they don’t have access to the original plain text version of our users’ passwords. Hashing algorithms typically fulfil the following criteria:

  • The same input always yields the same output.
  • A small change in the input string should yield a large change in the output hash.
  • It should be mathematically difficult to “guess” the input string given the output string.

When a user tries to log in to our application, we take the supplied password, apply the hashing algorithm and then compare it to the entries in our database. Because of the first criteria in the list above, if the password is correct it will match the hash we have saved in the database.

So how do we set the hashing scheme in Symfony? Once again, we use the configuration file…

# app/config/security.yml    
security:
    encoders:
        MyCompany\BlogBundle\Entity\User:
            algorithm:        sha1
            encode_as_base64: false
            iterations:       1
# ...

This configuration will hash passwords using the “SHA1″ algorithm, and it will apply the algorithm once. If we are to use the “in_memory” User Provider, the entries should now become:

# app/config/security.yml
security
# ...
    providers:
        in_memory:
            users:
                thomas: { password: 00ec36bbda403aaf23a2eff34964f54239d22e5c }
                ryan:   { password: e5e9fa1ba31ecd1ae84f75caaa474f3a663f05f4, 
                          roles: 'ROLE_USER' }
                admin:  { password: 12201fe5e202883bd45fc97e87366ea05183e0e4, 
                          roles: 'ROLE_ADMIN' }

    encoders:
        Symfony\Component\Security\Core\User\User:
            algorithm:        sha1
            encode_as_base64: false
            iterations:       1
# ...

Using the Database as a User Provider

Using the method described above, we can now hardcode user credentials into our “security.yml” file. But this isn’t what we typically want to do in our web applications. Instead, we typically want to use dynamic user accounts that can be added, deleted and modified as required. So typically we’ll use a database to maintain our list of user credentials. In order to use the Symfony security system with our database, we need to implement a custom User Provider.

We need to do two things to make this happen:

  1. Define a custom user entity in our model that implements UserInterface.
  2. Create a class to interact with the user repository that implements UserProviderInterface.

The User Interface

You’ll notice the fieldname “salt” mentioned above. If you haven’t seen this before, the salt is just a random string we add to the password before applying the hash. We usually use a timestamp or randomly generated alphanumeric value. For more information on hashes and salting, I recommend reading this great article.

The User Provider Interface

You’ll notice that the function signatures of UserInterface and UserProviderInterface implemented above are actually agnostic in terms of the source of the data. This means that we can just as easily create a User Provider that uses OAuth, LDAP or some other scheme to authenticate the user.

Implementing the User and User Repository for Database Storage

The first thing to do if we want to use a database to store our user accounts is to create the User entity in our Symfony application. The easiest way to do this is by using the command line utility. For ease of implementation, we’ll create a very basic User type that only has the fields id, username, email, password and salt. Run this command from the base-level of your Symfony folder:

$ php app/console doctrine:generate:entity

… and create an entity, MyCompanyBlogBundle:User with the following fields:

  • username string (255)
  • email string (255)
  • password string (255)
  • salt string (255)

To make things easier later, make sure you enter “YES” to the question “Do you want to generate an empty repository class?.” This will create two files for us:

src/MyCompany/BlogBundle/Entity/User.php
src/MyCompany/BlogBundle/Entity/UserRepository.php

Open both of these files and change them to the following:

Now that we’ve defined a custom User and User Repository type, we need to tell the Symfony security framework to use this for authenticating and authorising users. We do this using the “security.yml” file:

security:
    encoders:
        MyCompany\BlogBundle\Entity\User:
            algorithm:        sha1
            encode_as_base64: false
            iterations:       1

    providers:
        users:
            entity: { class: MyCompanyBlogBundle:User, property: username }

    firewalls:
        admin_area:
            pattern:    ^/admin
            http_basic: ~

Now if we try and navigate to any page under the “/admin” path, we’ll be presented with a HTTP basic prompt requesting our username and password. The provided details will then be authenticated using our User and UserRepository classes defined above.

There is one last piece of the puzzle, and that is making the prompt to accept visitors’ usernames and passwords a little “nicer.” After all, not many websites use the standard HTTP basic dialog box anymore. The way we do this is by defining a “form-based” login. We’ll discuss that in the next article.

Conclusion

After reading through this tutorial you should be ready to start using Symfony’s security framework in your web applications. But that isn’t the end of it – the security framework includes a lot of features not discussed in this article. To find out more, check out the official Symfony documentation.