The Application Setup and Introduction
In this first video we are going to take a look at the demo application we will be using throughout this short series on Securing parts of a Symfony application.
We will be building on the code from the FOSUserBundle video tutorial series. The reasoning for this is that we need to have the concept of Users before we can start restricting access for certain users or user roles.
That said, there is nothing special about the code from the FOSUserBundle series that you need to know - so if you are already comfortable with FOSUserBundle, or Users inside a Symfony application then you should have no problems with this video.
Securing Symfony Applications Through access_control
Entries
Likely the first way you will secure your application / website is through the access_control
list inside security.yml
.
This is the section of your security configuration where you can add in a list of regular expressions, and for every single request that hits your application, the requested route will be checked against the access_control
entry, and if matching will trigger the associated logic.
Sounds confusing?
Well, fortunately there's a really helpful Symfony Cookbook article on this very subject.
There's a variety of options you can configure in each access_control
entry. Again, the cookbook entry is a great place to read up on this further.
However, what I will say is that for most applications, especially when you are starting out, you can get away with knowing just two regular expressions for the vast majority of your access_control
entries. These are:
- ^ - beginning
- $ - ending
The characters are a little crazy, but the gist is really simple.
We might add in an entry as follows:
# app/config/security.yml
security:
access_control:
- { path: ^/admin/, role: ROLE_ADMIN }
This rule will match any of the following requests that come in to our application:
- http://your-site.com/admin/
- http://your-site.com/admin/something
- http://your-site.com/admin/some/long/path/here
And so on.
If your requested route matches any of these, the user you are currently logged in as will need to have the required role - ROLE_ADMIN
in this case - to be able to access the resource.
We could restrict this down to just /admin/
by changing the entry regular expression to:
# app/config/security.yml
security:
access_control:
- { path: ^/admin/$, role: ROLE_ADMIN }
Now the only route that will match is http://your-site.com/admin/
.
As in, start at the root: /
, and end after the text matches admin/
.
With these two characters, you can do most of what you need to do, most of the time :)
Careful though, as forgotting to use the ^
can cause unexpected results:
# app/config/security.yml
security:
access_control:
- { path: /admin/, role: ROLE_ADMIN }
Without the ^
, this would match:
- http://your-site.com/admin/
- http://your-site.com/admin/something
- http://your-site.com/anything/admin/something
Note the last entry? It still contains /admin/
, but because we never told our rule where to start, it will match anywhere in the string.
This can cause... unexpected situations.
You will very likely use the access_control
list (again, not to be confused with the Access Control List (ACL)) frequently.
It is extremely powerful when combined with further restrictions, such as the @Security
annotation, or Security Voters.
@Security
Annotation
Whilst the access_control
list can restrict large sections of your application from unauthorised access, it is not so great at fine grained control.
What if we had a requirement to restrict parts of our /admin/
section down further?
We likely could create some rather ahem, interesting regular expressions to meet our needs. However, whilst a regex like this:
/^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/
Will likely make sense to the developer as he or she is writing it, you can bet your bottom dollar that anyone - including that very same dev - will struggle to understand that in 6 months time. At least at first glance.
In case you were wondering, apparently that is a regex for matching IP addresses. I will take Google's word for it.
Instead, Symfony best practice steers us towards the @Security
annotation.
Now, in my opinion, annotations are not a prefferable option, if others exist. In this instance, I would really opt for a Security Voter - which we will come too shortly - because I can write tests for code, which I cannot do for annotations.
However, the @Security
annotation is, as mentioned, best practice, so let's investigate a little further, before jumping into more depth in the next video.
Reproducing our access_control
Entry with an @Security
Annotation
To recap, our access_control
entry is:
# app/config/security.yml
security:
access_control:
- { path: ^/admin/, role: ROLE_ADMIN }
This secures any controller that is serving pages under /admin/
.
We could recreate this on a per controller basis by using the @Security
annotation:
// src/AppBundle/Controller/AdminController.php
namespace AppBundle\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
/**
* @Route("/admin")
* @Security("has_role('ROLE_ADMIN')")
*/
class AdminController extends Controller
{
}
At this point, we could comment out or remove the access_control
entry for our /admin/
resource, and we would still be secure.
Personally, I don't like this approach.
If you have more than one controller for your admin area, you now have to reproduce your security setup on a per controller basis. This is not so great on the DRY front (Don't Repeat Yourself).
Your high level security rules now live all over your codebase, instead of in the obvious place - security.yml
. This same argument applies to your Routes as well, if using annotations.
This is why I would rather stick to using access_control
entries for restricting routes at a top level.
That's not to say that the @Security
annotation isn't useful though, as we shall see in the next video.