Restricting Routes To Only Logged In Users


In this video we are going to restrict the "Contact Us" form to be available only to logged in users. In doing so, we can make some assumptions about how the form is used - specifically that we will know who the user is at the time they send in their support request. This is nice as it ensures there's less for the user to fill in, and also gives us an excuse to learn a little more about a few parts of Symfony :)

Now that we have a way to register and log in, we can go ahead and restrict down parts of our site to only users who have the required levels of authorisation. To do this we will make use of the access_control section inside security.yml.

As a quick heads up, Symfony has both an access_control section (sometimes referred to as an access_control list), and a different concept called the Access Control List (ACL). Don't confuse these two, as they are not one and the same. But it's an easy mistake to make.

As with the firewalls section of security.yml, our access_control entries need to be put in a specific order. That is, when Symfony checks the entries configured under access_control, it will start at the top and work its way down until it finds a match.

Therefore, we need our most specific rules to be at the top, and less specific rules further down the bottom.

My plan is fairly simple.

I want to restrict everything that is not related to Registration and Log in.

The only people who can access any page on my site should be user's with the role of ROLE_USER.

However, I want to ensure that the routes we created for Login and Registration are available to everyone - in other words, those users with the role of IS_AUTHENTICATED_ANONYMOUSLY.

Here's the access_control section I have added:

# /app/config/security.yml

security:

    # * snip *

    access_control:
        - { path: ^/registration-form-submission$, role: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/(login|register)$, role: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/, role: ROLE_USER }

As you will see in the video, mistakes are easily made. I forgot I had split my sign up process into two different controller actions, and the action being POSTed too required ROLE_USER. Oops. So yeah, be careful :)

Anyway, to clarify:

- { path: ^/registration-form-submission$, role: IS_AUTHENTICATED_ANONYMOUSLY }

This means if the user browses to a route starting with (^) a slash /, and then containing the exact text "registration-form-submission", and nothing further ($) then for this path the user does not need to be logged in role: IS_AUTHENTICATED_ANONYMOUSLY.

I appreciate that's a little confusing. The path section takes a regular expression.

If we didn't use the anchors of ^ and $, we could end up with unwanted matches occurring. By using the anchors we can be extremely confident we won't accidentally match a nested route as our application grows.

The next line:

- { path: ^/(login|register)$, role: IS_AUTHENTICATED_ANONYMOUSLY }

This could be merged with the first line. However, it would make the line too long, so I broke the rule down.

This uses an 'OR' rule (|) to say match either /login or /register. If either are matched, again, the user need not be logged in to view this content.

Finally, the last rule is a generic catch-all. It says anything starting with (^) a slash (/) will need the user to have the role of ROLE_USER to view the content. If they don't, they will be redirected to the login page for the main firewall. Why? Because the main firewall covers ^/.

If we take a look at our main firewall entry again:

# /app/config/security.yml

security:

    firewalls:

        main:
            pattern: ^/
            provider: chain_provider
            form_login:
                username_parameter: '_username'
                login_path: login
                check_path: login
            logout: true
            anonymous: ~

Not only does our firewall match the pattern of ^/, but we also define the anonymous key which means that if there is a match on this firewall and the route is not covered by a more strict rule inside access_control, then at the very least, the user will have received the role of IS_AUTHENTICATED_ANONYMOUSLY, which is precisely what we need to make our insecure routes work as expected.

Code For This Course

Get the code for this course.

Episodes