Symfony's security system is incredibly powerful, but it can also be confusing to set up. In this article 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.
Fetching the current User object.
These are followed by a number of small (but still captivating) sections, like logging out and encoding user passwords.
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.
You'll learn later how to deny access to certain URLs or controllers.
Security is highly configurable and there's a Security Configuration Reference that shows all of the options with some extra explanation.
The main job of a firewall is to configure how your users will authenticate. Will they use a login form? HTTP basic authentication? An API token? All of the above?
You'll learn more about this ROLE_ADMIN thing and denying access later in the 2) Denying Access, Roles and other Authorization section.
But who can you login as? Where do users come from?
Want to use a traditional login form? Great! See How to Build a Traditional Login Form. What other methods are supported? See the Configuration Reference or build your own.
If your application logs users in via a third-party service such as Google, Facebook or Twitter, check out the HWIOAuthBundle community bundle.
When you type in your username, Symfony needs to load that user's information from somewhere. This is called a "user provider", and you're in charge of configuring it. Symfony has a built-in way to load users from the database, or you can create your own user 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).
See How to Use multiple User Providers for all the details about multiple providers setup.
Try to login using username admin and password kitten. You should see an error!
No encoder has been configured for account "Symfony\Component\Security\Core\User\User"
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.
Because this URL requires ROLE_ADMIN, if you had logged in as ryan, this would deny you access. More on that later (Securing URL patterns (access_control)).
If you'd like to load your users via the Doctrine ORM, that's easy! See How to Load Security Users from the Database (the Entity Provider) for all the details.
$ composer require ircmaxell/password-compat "~1.0"
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.
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.
It's also possible to use different hashing algorithms on a user-by-user basis. See How to Choose the Password Encoder Algorithm Dynamically for more details.
Congratulations! You now have a working authentication system that uses HTTP basic auth and loads users right from the security.yml file.
Learn how to deny access, load the User object and deal with roles in the Authorization section.
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 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.
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.
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.
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.
in your code via the security.authorization_checker service.
This is great for securing entire sections, but you'll also probably want to secure your individual controllers as well.
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.
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.
To learn about all of this, see How Does the Security access_control Work?.
// The second parameter is used to specify on what object the role is tested.
New in version 2.6: The denyAccessUnlessGranted() method was introduced in Symfony 2.6. Previously (and still now), you could check access directly and throw the AccessDeniedException as shown in the example above).
New in version 2.6: The security.authorization_checker service was introduced in Symfony 2.6. Prior to Symfony 2.6, you had to use the isGranted() method of the security.context service.
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.
For more information, see the FrameworkExtraBundle documentation.
If you use this function and you are not behind a firewall, an exception will be thrown. Again, it's almost always a good idea to have a main firewall that covers all URLs (as shown before in this article).
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.
For more information see How to Secure any Service or Method in your Application.
You can of course also use this in access_control.
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.
IS_AUTHENTICATED_ANONYMOUSLY: All users (even anonymous ones) have this - this is useful when whitelisting URLs to guarantee access - some details are in How Does the Security access_control Work?.
For more details on expressions and security, see Security: Complex Access Controls with Expressions.
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.
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.
New in version 2.6: The security.token_storage service was introduced in Symfony 2.6. Prior to Symfony 2.6, you had to use the getToken() method of the security.context service.
The user will be an object and the class of that object will depend on your user provider.
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.
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.
And that's it! By sending a user to /logout (or whatever you configure the path to be), Symfony will un-authenticate the current user.
Once the user has been logged out, they will be redirected to whatever path is defined by the target parameter above (e.g. the homepage).
If you need to do something more interesting after logging out, you can specify a logout success handler by adding a success_handler key and pointing it to a service id of a class that implements LogoutSuccessHandlerInterface. See Security Configuration Reference.
In the above configuration, users with ROLE_ADMIN role will also have the ROLE_USER role. The ROLE_SUPER_ADMIN role has ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH and ROLE_USER (inherited from ROLE_ADMIN).
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.