Security

Symfony's security system is incredibly powerful, but it can also be confusing
to set up. In this chapter, you'll learn how to set up your application's security
step-by-step, from configuring your firewall and how you load users to denying
access and fetching the User object. Depending on what you need, sometimes
the initial setup can be tough. But once it's done, Symfony's security system
is both flexible and (hopefully) fun to work with.

Since there's a lot to talk about, this chapter is organized into a few big
sections:

The firewalls key is the heart of your security configuration. The
dev firewall isn't important, it just makes sure that Symfony's development
tools - which live under URLs like /_profiler and /_wdt aren't blocked
by your security.

All other URLs will be handled by the default firewall (no pattern
key means it matches all URLs). You can think of the firewall like your
security system, and so it usually makes sense to have just one main firewall.
But this does not mean that every URL requires authentication - the anonymous
key takes care of this. In fact, if you go to the homepage right now, you'll
have access and you'll see that you're "authenticated" as anon.. Don't
be fooled by the "Yes" next to Authenticated, you're just an anonymous user:

Simple! To try this, you need to require the user to be logged in to see
a page. To make things interesting, create a new page at /admin. For
example, if you use annotations, create something like this:

The easiest (but most limited) way, is to configure Symfony to load hardcoded
users directly from the security.yml file itself. This is called an "in memory"
provider, but it's better to think of it as an "in configuration" provider:

Like with firewalls, you can have multiple providers, but you'll
probably only need one. If you do have multiple, you can configure which
one provider to use for your firewall under its provider key (e.g.
provider: in_memory).

User providers load user information and put it into a User object. If
you load users from the database
or some other source, you'll
use your own custom User class. But when you use the "in memory" provider,
it gives you a Symfony\Component\Security\Core\User\User object.

Whatever your User class is, you need to tell Symfony what algorithm was
used to encode the passwords. In this case, the passwords are just plaintext,
but in a second, you'll change this to use bcrypt.

If you refresh now, you'll be logged in! The web debug toolbar even tells
you who you are and what roles you have:

Everything will now work exactly like before. But if you have dynamic users
(e.g. from a database), how can you programmatically encode the password
before inserting them into the database? Don't worry, see
How to Manually Encode a Password for details.

Tip

Supported algorithms for this method depend on your PHP version, but
include the algorithms returned by the PHP function hash_algos
as well as a few others (e.g. bcrypt). See the encoders key in the
Security Reference Section
for examples.

Users can now login to your app using http_basic or some other method.
Great! Now, you need to learn how to deny access and work with the User object.
This is called authorization, and its job is to decide if a user can
access some resource (a URL, a model object, a method call, ...).

The process of authorization has two different sides:

The user receives a specific set of roles when logging in (e.g. ROLE_ADMIN).

You add code so that a resource (e.g. URL, controller) requires a specific
"attribute" (most commonly a role like ROLE_ADMIN) in order to be
accessed.

Tip

In addition to roles (e.g. ROLE_ADMIN), you can protect a resource
using other attributes/strings (e.g. EDIT) and use voters or Symfony's
ACL system to give these meaning. This might come in handy if you need
to check if user A can "EDIT" some object B (e.g. a Product with id 5).
See Access Control Lists (ACLs): Securing individual Database Objects.

When a user logs in, they receive a set of roles (e.g. ROLE_ADMIN). In
the example above, these are hardcoded into security.yml. If you're
loading users from the database, these are probably stored on a column
in your table.

Caution

All roles you assign to a user must begin with the ROLE_ prefix.
Otherwise, they won't be handled by Symfony's security system in the
normal way (i.e. unless you're doing something advanced, assigning a
role like FOO to a user and then checking for FOO as described
below will not work).

Roles are simple, and are basically strings that you invent and use as needed.
For example, if you need to start limiting access to the blog admin section
of your website, you could protect that section using a ROLE_BLOG_ADMIN
role. This role doesn't need to be defined anywhere - you can just start using
it.

Tip

Make sure every user has at least one role, or your user will look
like they're not authenticated. A common convention is to give every
user ROLE_USER.

You can also specify a role hierarchy where
some roles automatically mean that you also have other roles.

You can define as many URL patterns as you need - each is a regular expression.
BUT, only one will be matched. Symfony will look at each starting
at the top, and stop as soon as it finds one access_control entry that
matches the URL.

Prepending the path with ^ means that only URLs beginning with the
pattern are matched. For example, a path of simply /admin (without
the ^) would match /admin/foo but would also match URLs like /foo/admin.

Understanding how access_control Works

The access_control section is very powerful, but it can also be dangerous
(because it involves security) if you don't understand how it works.
In addition to the URL, the access_control can match on IP address,
host name and HTTP methods. It can also be used to redirect a user to
the https version of a URL pattern.

// ...publicfunctionhelloAction($name){// The second parameter is used to specify on what object the role is tested.$this->denyAccessUnlessGranted('ROLE_ADMIN',null,'Unable to access this page!');// Old way :// if (false === $this->get('security.authorization_checker')->isGranted('ROLE_ADMIN')) {// throw $this->createAccessDeniedException('Unable to access this page!');// }// ...}

In both cases, a special
AccessDeniedException
is thrown, which ultimately triggers a 403 HTTP response inside Symfony.

That's it! If the user isn't logged in yet, they will be asked to login (e.g.
redirected to the login page). If they are logged in, but do not have the
ROLE_ADMIN role, they'll be shown the 403 access denied page (which you can
customize). If they are logged in
and have the correct roles, the code will be executed.

Thanks to the SensioFrameworkExtraBundle, you can also secure your controller
using annotations:

Anything in Symfony can be protected by doing something similar to the code
used to secure a controller. For example, suppose you have a service (i.e. a
PHP class) whose job is to send emails. You can restrict use of this class - no
matter where it's being used from - to only certain users.

So far, you've checked access based on roles - those strings that start with
ROLE_ and are assigned to users. But if you only want to check if a
user is logged in (you don't care about roles), then you can use
IS_AUTHENTICATED_FULLY:

IS_AUTHENTICATED_FULLY isn't a role, but it kind of acts like one, and every
user that has successfully logged in will have this. In fact, there are three
special attributes like this:

IS_AUTHENTICATED_REMEMBERED: All logged in users have this, even
if they are logged in because of a "remember me cookie". Even if you don't
use the remember me functionality,
you can use this to check if the user is logged in.

IS_AUTHENTICATED_FULLY: This is similar to IS_AUTHENTICATED_REMEMBERED,
but stronger. Users who are logged in only because of a "remember me cookie"
will have IS_AUTHENTICATED_REMEMBERED but will not have IS_AUTHENTICATED_FULLY.

Imagine you are designing a blog where users can comment on your posts. You
also want a user to be able to edit their own comments, but not those of
other users. Also, as the admin user, you yourself want to be able to edit
all comments.

To accomplish this you have 2 options:

Voters allow you to write own business logic
(e.g. the user can edit this post because they were the creator) to determine
access. You'll probably want this option - it's flexible enough to solve the
above situation.

ACLs allow you to create a database structure
where you can assign any arbitrary user any access (e.g. EDIT, VIEW)
to any object in your system. Use this if you need an admin user to be
able to grant customized access across your system via some admin interface.

In both cases, you'll still deny access using methods similar to what was
shown above.

After authentication, the User object of the current user can be accessed
via the security.token_storage service. From inside a controller, this will
look like:

1
2
3
4
5
6
7
8
9
10
11

publicfunctionindexAction(){if(!$this->get('security.authorization_checker')->isGranted('IS_AUTHENTICATED_FULLY')){throw$this->createAccessDeniedException();}$user=$this->getUser();// the above is a shortcut for this$user=$this->get('security.token_storage')->getToken()->getUser();}

Tip

The user will be an object and the class of that object will depend on
your user provider.

Now you can call whatever methods are on your User object. For example,
if your User object has a getFirstName() method, you could use that:

It's important to check if the user is authenticated first. If they're not,
$user will either be null or the string anon.. Wait, what? Yes,
this is a quirk. If you're not logged in, the user is technically the string
anon., though the getUser() controller shortcut converts this to
null for convenience.

The point is this: always check to see if the user is logged in before using
the User object, and use the isGranted() method (or
access_control) to do this:

1
2
3
4
5
6
7
8
9

// yay! Use this to see if the user is logged inif(!$this->get('security.authorization_checker')->isGranted('IS_AUTHENTICATED_FULLY')){throw$this->createAccessDeniedException();}// boo :(. Never check for the User object to see if they're logged inif($this->getUser()){}

Notice that when using http-basic authenticated firewalls, there is no
real way to log out : the only way to log out is to have the browser
stop sending your name and password on every request. Clearing your
browser cache or restarting your browser usually helps. Some web developer
tools might be helpful here too.

Usually, you'll also want your users to be able to log out. Fortunately,
the firewall can handle this automatically for you when you activate the
logout config parameter:

Woh! Nice work! You now know more than the basics of security. The hardest
parts are when you have custom requirements: like a custom authentication
strategy (e.g. API tokens), complex authorization logic and many other things
(because security is complex!).

Fortunately, there are a lot of articles aimed at describing many of these
situations. Also, see the Security Reference Section.
Many of the options don't have specific details, but seeing the full possible
configuration tree may be useful.