Symfony Security for Beginners


In this video we are taking our first look at security inside Symfony.

Security - in my opinion - is the most confusing and complicated part of the whole Symfony learning process.

That said, you don't need to know every last detail to get a security system in place.

By the end of this video you will have seen how we can authenticate and authorise our site visitors against users we have defined, and we will cover how Symfony doesn't really bother where our user accounts come from, so long as they follow the expected conventions.

We will also briefly cover how user management typically happens in a real world Symfony application - hint: FOSUserBundle.

Lets Get Secure

Most of our work will be done in app/config/security.yml.

The good news is, it's a lot of copy / paste from the official Symfony manual. OF course, the trick is knowing which bits to copy and paste - and why - but we will get to that.

There's a bit of a new terminology to get your head around.

Authentication / Authorisation

Authentication is the process of confirming you are who you say you are.

Authorisation is the process of allowing / granting or denying access to a given resource (read: a url) depending on whether you have the right permissions or not.

Let's take a supermarket as an example.

Everyone has access to the supermarket. You don't need to show your ID / passport for entry. In Symfony terms we would say the supermarket allows anonymous access.

To gain access to the staff area you need to be an employee. Lets imagine there is a door to the staff area with a security guard. To get past the security guard you must show your ID badge.

The security guard uses your badge to authenticate you.

With a valid ID badge, you are authorised to enter the staff area.

However, unless you have been given extra permissions, you are not authorised to enter the room where they keep all the money.

Ultimately, simply because you are authenticated does not necessarily mean you will always be authorised to do something.

A point of confusion here is that even anonymous users are seen as being authenticated (IS_AUTHENTICATED_ANONYMOUSLY) - more on that in the Firewall section below.

Providers

This is a confusing name for a simple concept.

For our users to be authenticated, somewhere we must have a list of known users.

Symfony ultimately does not care where that 'list' of users come from, but it does care about the format the 'list'. For extra geek points, you should know that the 'list' must be a class that implements UserProviderInterface.

With a standard install of Symfony, the User Provider can get its list of users from either the in-memory user provider, or the entity user provider.

Another way of saying this would be that our users are provided from app/config/security.yml (in_memory), or from database entities (user records in our database).

You can also create your own User Provider, if neither of these suit your needs.

Going back to our security guard example, we might imagine that our security guard has been given a list of valid badge IDs. He would check the printout on his clipboard (clipboard user provider) and ensure our badge ID is still active.

Firewall

Firewalls are pretty much the main part of this whole process.

You always need at least one firewall.

If you are using a standard Symfony install you will have two firewalls set up by default:

# app/config/security.yml

    *snip*

    firewalls:
        dev:
            pattern: ^/(_(profiler|wdt)|css|images|js)/
            security: false

        default:
            anonymous: ~

The dev firewall ensures you get access to the developer toolbar, so you can safely ignore that for now.

As mentioned above, you may have noticed that even though you haven't had to log in, you are still seen as an authenticated user by Symfony (on the app_dev.php toolbar).

This is because of the default firewall entry.

According to the generated firewalls entry initially created when installing Symfony, all routes / urls on our site are covered by the default firewall.

This default firewall rule is a catch all rule. We can tell this because unlike the dev firewall, the default rule does not have a pattern, so is matching everything. A pattern would be a regular expression to match specific URI's.

The default rule is saying anyone who hits this rule will be authenticated anonymously. This means you are in a part of the site that is protected by a firewall, but you haven't logged in.

Kinda crazy.

ACL (Access Control List)

Access Control Lists are very handy ways of securing your site by specifying users trying to access a URI must have specific roles.

# app/config/security.yml
security:
    # ...
    access_control:
        - { path: ^/admin/users, roles: ROLE_SUPER_ADMIN }
        - { path: ^/admin, roles: ROLE_ADMIN }

Again, it's based on regular expressions. As a side note, whilst not free, I highly recommend RegexBuddy if you find Regex confusing.

Here we are saying: match any URI starting with (^) /admin/users, and ensure that the user account trying to access this URI has the role of ROLE_SUPER_ADMIN.

The next rule does the same, but matches a less specific route, and requires lesser permissions.

The important point here is that the ACL runs top to bottom.

More specific rules need to go at the top.

That is to say, if we reversed these rules:

# app/config/security.yml
security:
    # wrong!
    access_control:
        - { path: ^/admin, roles: ROLE_ADMIN }
        - { path: ^/admin/users, roles: ROLE_SUPER_ADMIN }

The ^/admin/users rule would never be hit.

However, this would cause a security issue as now users with ROLE_ADMIN would be able to access /admin/users.

The first rule to match is the one that wins!

User Roles

User roles are just strings that we, as developers / the business, can make up to suit our needs.

The main thing to remember is that you must begin any role with the prefix of ROLE_ and then whatever you want.

Once you have decided on some roles, you can use them in conjunction with your ACL, and so long as your user accounts have those roles, all will be dandy.

More to be learned about roles right here, but no need to over complicate this one.

Encoders

Symfony needs a way to determine how to encode and decode your password.

For this purpose we need to specify which password encoder we are using.

# app/config/security.yml
security:
    encoders:
        Symfony\Component\Security\Core\User\User: plaintext

Of course, in the real world you don't want your passwords stored in plaintext, so instead it is best practice to use bcrypt:

# app/config/security.yml
security:
    encoders:
        Symfony\Component\Security\Core\User\User:
            algorithm: bcrypt
            cost: 12

More on this here.

Episodes