Security

Security

Screencast

Do you prefer video tutorials? Check out the Symfony Security screencast series.

Symfony’s security system is incredibly powerful, but it can also be confusing to set up. Don’t worry! In this article, you’ll learn how to set up your app’s security system step-by-step:

  1. Installing security support;
  2. Create your User Class;
  3. Authentication & Firewalls;
  4. Denying access to your app (authorization);
  5. Fetching the current User object.

A few other important topics are discussed after.

1) Installation

In applications using Symfony Flex, run this command to install the security feature before using it:

  1. $ composer require symfony/security-bundle

Tip

A new authenticator-based Security was introduced in Symfony 5.1, which will replace security in Symfony 6.0. This system is almost fully backwards compatible with the current Symfony security, add this line to your security configuration to start using it:

  • YAML

    1. # config/packages/security.yaml
    2. security:
    3. enable_authenticator_manager: true
    4. # ...
  • XML

    1. <!-- config/packages/security.xml -->
    2. <?xml version="1.0" encoding="UTF-8"?>
    3. <srv:container xmlns="http://symfony.com/schema/dic/security"
    4. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    5. xmlns:srv="http://symfony.com/schema/dic/services"
    6. xsi:schemaLocation="http://symfony.com/schema/dic/services
    7. https://symfony.com/schema/dic/services/services-1.0.xsd
    8. http://symfony.com/schema/dic/security
    9. https://symfony.com/schema/dic/security/security-1.0.xsd">
    10. <config enable-authenticator-manager="true">
    11. <!-- ... -->
    12. </config>
    13. </srv:container>
  • PHP

    1. // config/packages/security.php
    2. use Symfony\Config\SecurityConfig;
    3. return static function (SecurityConfig $security) {
    4. $security->enableAuthenticatorManager(true);
    5. // ...
    6. };

2a) Create your User Class

No matter how you will authenticate (e.g. login form or API tokens) or where your user data will be stored (database, single sign-on), the next step is always the same: create a “User” class. The easiest way is to use the MakerBundle.

Let’s assume that you want to store your user data in the database with Doctrine:

  1. $ php bin/console make:user
  2. The name of the security user class (e.g. User) [User]:
  3. > User
  4. Do you want to store user data in the database (via Doctrine)? (yes/no) [yes]:
  5. > yes
  6. Enter a property name that will be the unique "display" name for the user (e.g.
  7. email, username, uuid [email]
  8. > email
  9. Does this app need to hash/check user passwords? (yes/no) [yes]:
  10. > yes
  11. created: src/Entity/User.php
  12. created: src/Repository/UserRepository.php
  13. updated: src/Entity/User.php
  14. updated: config/packages/security.yaml

That’s it! The command asks several questions so that it can generate exactly what you need. The most important is the User.php file itself. The only rule about your User class is that it must implement Symfony\Component\Security\Core\User\UserInterface. Feel free to add any other fields or logic you need. If your User class is an entity (like in this example), you can use the make:entity command to add more fields. Also, make sure to make and run a migration for the new entity:

  1. $ php bin/console make:migration
  2. $ php bin/console doctrine:migrations:migrate

2b) The “User Provider”

In addition to your User class, you also need a “User provider”: a class that helps with a few things, like reloading the User data from the session and some optional features, like remember me and impersonation.

Fortunately, the make:user command already configured one for you in your security.yaml file under the providers key:

  • YAML

    1. # config/packages/security.yaml
    2. security:
    3. # ...
    4. providers:
    5. # used to reload user from session & other features (e.g. switch_user)
    6. app_user_provider:
    7. entity:
    8. class: App\Entity\User
    9. property: email
  • XML

    1. <!-- config/packages/security.xml -->
    2. <?xml version="1.0" encoding="UTF-8" ?>
    3. <srv:container xmlns="http://symfony.com/schema/dic/security"
    4. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    5. xmlns:srv="http://symfony.com/schema/dic/services"
    6. xsi:schemaLocation="http://symfony.com/schema/dic/services
    7. https://symfony.com/schema/dic/services/services-1.0.xsd">
    8. <config>
    9. <!-- used to reload user from session & other features (e.g. switch-user) -->
    10. <provider name="app_user_provider">
    11. <entity class="App\Entity\User" property="email"/>
    12. </provider>
    13. </config>
    14. </srv:container>
  • PHP

    1. // config/packages/security.php
    2. use App\Entity\User;
    3. use Symfony\Config\SecurityConfig;
    4. return static function (SecurityConfig $security) {
    5. // ...
    6. // used to reload user from session & other features (e.g. switch_user)
    7. $security->provider('app_user_provider')
    8. ->entity()
    9. ->class(User::class)
    10. ->property('email');
    11. };

If your User class is an entity, you don’t need to do anything else. But if your class is not an entity, then make:user will also have generated a UserProvider class that you need to finish. Learn more about user providers here: User Providers.

2c) Hashing Passwords

Not all applications have “users” that need passwords. If your users have passwords, you can control how those passwords are hashed in security.yaml. The make:user command will pre-configure this for you:

  • YAML

    1. # config/packages/security.yaml
    2. security:
    3. # ...
    4. password_hashers:
    5. # use your user class name here
    6. App\Entity\User:
    7. # Use native password hasher, which auto-selects the best
    8. # possible hashing algorithm (starting from Symfony 5.3 this is "bcrypt")
    9. algorithm: auto
  • XML

    1. <!-- config/packages/security.xml -->
    2. <?xml version="1.0" encoding="UTF-8" ?>
    3. <srv:container xmlns="http://symfony.com/schema/dic/security"
    4. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    5. xmlns:srv="http://symfony.com/schema/dic/services"
    6. xsi:schemaLocation="http://symfony.com/schema/dic/services
    7. https://symfony.com/schema/dic/services/services-1.0.xsd
    8. http://symfony.com/schema/dic/security
    9. https://symfony.com/schema/dic/security/security-1.0.xsd">
    10. <config>
    11. <!-- ... -->
    12. <security:password-hasher class="App\Entity\User"
    13. algorithm="auto"
    14. cost="12"/>
    15. <!-- ... -->
    16. </config>
    17. </srv:container>
  • PHP

    1. // config/packages/security.php
    2. use App\Entity\User;
    3. use Symfony\Config\SecurityConfig;
    4. return static function (SecurityConfig $security) {
    5. // ...
    6. $security->passwordHasher(User::class)
    7. ->algorithm('auto')
    8. ->cost(12);
    9. // ...
    10. };

New in version 5.3: The password_hashers option was introduced in Symfony 5.3. In previous versions it was called encoders.

Now that Symfony knows how you want to hash the passwords, you can use the UserPasswordHasherInterface service to do this before saving your users to the database.

For example, by using DoctrineFixturesBundle, you can create dummy database users:

  1. $ php bin/console make:fixtures
  2. The class name of the fixtures to create (e.g. AppFixtures):
  3. > UserFixtures

Use this service to hash the passwords:

  1. // src/DataFixtures/UserFixtures.php
  2. + use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
  3. // ...
  4. class UserFixtures extends Fixture
  5. {
  6. + private $passwordHasher;
  7. + public function __construct(UserPasswordHasherInterface $passwordHasher)
  8. + {
  9. + $this->passwordHasher = $passwordHasher;
  10. + }
  11. public function load(ObjectManager $manager)
  12. {
  13. $user = new User();
  14. // ...
  15. + $user->setPassword($this->passwordHasher->hashPassword(
  16. + $user,
  17. + 'the_new_password'
  18. + ));
  19. // ...
  20. }
  21. }

You can manually hash a password by running:

  1. $ php bin/console security:hash-password

3a) Authentication & Firewalls

New in version 5.1: The lazy: true option was introduced in Symfony 5.1. Prior to version 5.1, it was enabled using anonymous: lazy

The security system is configured in config/packages/security.yaml. The most important section is firewalls:

  • YAML

    1. # config/packages/security.yaml
    2. security:
    3. firewalls:
    4. dev:
    5. pattern: ^/(_(profiler|wdt)|css|images|js)/
    6. security: false
    7. main:
    8. anonymous: true
    9. lazy: true
  • XML

    1. <!-- config/packages/security.xml -->
    2. <?xml version="1.0" encoding="UTF-8" ?>
    3. <srv:container xmlns="http://symfony.com/schema/dic/security"
    4. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    5. xmlns:srv="http://symfony.com/schema/dic/services"
    6. xsi:schemaLocation="http://symfony.com/schema/dic/services
    7. https://symfony.com/schema/dic/services/services-1.0.xsd
    8. http://symfony.com/schema/dic/security
    9. https://symfony.com/schema/dic/security/security-1.0.xsd">
    10. <config>
    11. <firewall name="dev"
    12. pattern="^/(_(profiler|wdt)|css|images|js)/"
    13. security="false"/>
    14. <firewall name="main"
    15. anonymous="true"
    16. lazy="true"/>
    17. </config>
    18. </srv:container>
  • PHP

    1. // config/packages/security.php
    2. use Symfony\Config\SecurityConfig;
    3. return static function (SecurityConfig $security) {
    4. $security->firewall('dev')
    5. ->pattern('^/(_(profiler|wdt)|css|images|js)/')
    6. ->security(false);
    7. $security->firewall('main')
    8. ->lazy(true)
    9. ->anonymous();
    10. };

A “firewall” is your authentication system: the configuration below it defines how your users will be able to authenticate (e.g. login form, API token, etc).

Only one firewall is active on each request: Symfony uses the pattern key to find the first match (you can also match by host or other things). The dev firewall is really a fake firewall: it makes sure that you don’t accidentally block Symfony’s dev tools - which live under URLs like /_profiler and /_wdt.

All real URLs are handled by the main firewall (no pattern key means it matches all URLs). A firewall can have many modes of authentication, in other words many ways to ask the question “Who are you?”. Often, the user is unknown (i.e. not logged in) when they first visit your website. The anonymous mode, if enabled, is used for these requests.

In fact, if you go to the homepage right now, you will have access and you’ll see that you’re “authenticated” as anon.. The firewall verified that it does not know your identity, and so, you are anonymous:

_images/anonymous_wdt.png

It means any request can have an anonymous token to access some resource, while some actions (i.e. some pages or buttons) can still require specific privileges. A user can then access a form login without being authenticated as a unique user (otherwise an infinite redirection loop would happen asking the user to authenticate while trying to doing so).

You’ll learn later how to deny access to certain URLs, controllers, or part of templates.

Tip

The lazy anonymous mode prevents the session from being started if there is no need for authorization (i.e. explicit check for a user privilege). This is important to keep requests cacheable (see HTTP Cache).

Note

If you do not see the toolbar, install the profiler with:

  1. $ composer require --dev symfony/profiler-pack

Now that we understand our firewall, the next step is to create a way for your users to authenticate!

3b) Authenticating your Users

Authentication in Symfony can feel a bit “magic” at first. That’s because, instead of building a route & controller to handle login, you’ll activate an authentication provider: some code that runs automatically before your controller is called.

Symfony has several built-in authentication providers. If your use-case matches one of these exactly, great! But, in most cases - including a login form - we recommend building a Guard Authenticator: a class that allows you to control every part of the authentication process (see the next section).

Tip

If your application logs users in via a third-party service such as Google, Facebook or Twitter (social login), check out the HWIOAuthBundle community bundle.

Guard Authenticators

Deprecated since version 5.3: Guard authenticators are deprecated since Symfony 5.3 in favor of the new authenticator-based system.

A Guard authenticator is a class that gives you complete control over your authentication process. There are many different ways to build an authenticator; here are a few common use-cases:

Limiting Login Attempts

New in version 5.2: Login throttling was introduced in Symfony 5.2.

Symfony provides basic protection against brute force login attacks if you’re using the authenticator-based authenticators. You must enable this using the login_throttling setting:

  • YAML

    1. # config/packages/security.yaml
    2. security:
    3. enable_authenticator_manager: true
    4. firewalls:
    5. # ...
    6. main:
    7. # ...
    8. # by default, the feature allows 5 login attempts per minute
    9. login_throttling: null
    10. # configure the maximum login attempts (per minute)
    11. login_throttling:
    12. max_attempts: 3
    13. # configure the maximum login attempts in a custom period of time
    14. login_throttling:
    15. max_attempts: 3
    16. interval: '15 minutes'
    17. # use a custom rate limiter via its service ID
    18. login_throttling:
    19. limiter: app.my_login_rate_limiter
  • XML

    1. <!-- config/packages/security.xml -->
    2. <?xml version="1.0" encoding="UTF-8"?>
    3. <srv:container xmlns="http://symfony.com/schema/dic/security"
    4. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    5. xmlns:srv="http://symfony.com/schema/dic/services"
    6. xsi:schemaLocation="http://symfony.com/schema/dic/services
    7. https://symfony.com/schema/dic/services/services-1.0.xsd
    8. http://symfony.com/schema/dic/security
    9. https://symfony.com/schema/dic/security/security-1.0.xsd">
    10. <config enable-authenticator-manager="true">
    11. <!-- ... -->
    12. <firewall name="main">
    13. <!-- by default, the feature allows 5 login attempts per minute -->
    14. <login-throttling/>
    15. <!-- configure the maximum login attempts (per minute) -->
    16. <login-throttling max-attempts="3"/>
    17. <!-- configure the maximum login attempts in a custom period of time -->
    18. <login-throttling max-attempts="3" interval="15 minutes"/>
    19. <!-- use a custom rate limiter via its service ID -->
    20. <login-throttling limiter="app.my_login_rate_limiter"/>
    21. </firewall>
    22. </config>
    23. </srv:container>
  • PHP

    1. // config/packages/security.php
    2. use Symfony\Config\SecurityConfig;
    3. return static function (SecurityConfig $security) {
    4. $security->enableAuthenticatorManager(true);
    5. // ...
    6. $mainFirewall = $security->firewall('main');
    7. // by default, the feature allows 5 login attempts per minute
    8. $mainFirewall
    9. ->loginThrottling();
    10. // configure the maximum login attempts (per minute)
    11. $mainFirewall
    12. ->loginThrottling()
    13. ->maxAttempts(3)
    14. ->interval('15 minutes');
    15. // configure the maximum login attempts in a custom period of time
    16. $mainFirewall
    17. ->loginThrottling()
    18. ->maxAttempts(3);
    19. };

New in version 5.3: The login_throttling.interval option was introduced in Symfony 5.3.

By default, login attempts are limited on max_attempts (default: 5) failed requests for IP address + username and 5 * max_attempts failed requests for IP address. The second limit protects against an attacker using multiple usernames from bypassing the first limit, without distrupting normal users on big networks (such as offices).

Tip

Limiting the failed login attempts is only one basic protection against brute force attacks. The OWASP Brute Force Attacks guidelines mention several other protections that you should consider depending on the level of protection required.

If you need a more complex limiting algorithm, create a class that implements Symfony\Component\HttpFoundation\RateLimiter\RequestRateLimiterInterface (or use Symfony\Component\Security\Http\RateLimiter\DefaultLoginRateLimiter) and set the limiter option to its service ID:

  • YAML

    1. # config/packages/security.yaml
    2. framework:
    3. rate_limiter:
    4. # define 2 rate limiters (one for username+IP, the other for IP)
    5. username_ip_login:
    6. policy: token_bucket
    7. limit: 5
    8. rate: { interval: '5 minutes' }
    9. ip_login:
    10. policy: sliding_window
    11. limit: 50
    12. interval: '15 minutes'
    13. services:
    14. # our custom login rate limiter
    15. app.login_rate_limiter:
    16. class: Symfony\Component\Security\Http\RateLimiter\DefaultLoginRateLimiter
    17. arguments:
    18. # globalFactory is the limiter for IP
    19. $globalFactory: '@limiter.ip_login'
    20. # localFactory is the limiter for username+IP
    21. $localFactory: '@limiter.username_ip_login'
    22. security:
    23. firewalls:
    24. main:
    25. # use a custom rate limiter via its service ID
    26. login_throttling:
    27. limiter: app.login_rate_limiter
  • XML

    1. <!-- config/packages/security.xml -->
    2. <?xml version="1.0" encoding="UTF-8"?>
    3. <srv:container xmlns="http://symfony.com/schema/dic/security"
    4. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    5. xmlns:framework="http://symfony.com/schema/dic/symfony"
    6. xmlns:srv="http://symfony.com/schema/dic/services"
    7. xsi:schemaLocation="http://symfony.com/schema/dic/services
    8. https://symfony.com/schema/dic/services/services-1.0.xsd
    9. http://symfony.com/schema/dic/symfony
    10. https://symfony.com/schema/dic/symfony/symfony-1.0.xsd
    11. http://symfony.com/schema/dic/security
    12. https://symfony.com/schema/dic/security/security-1.0.xsd">
    13. <framework:config>
    14. <framework:rate-limiter>
    15. <!-- define 2 rate limiters (one for username+IP, the other for IP) -->
    16. <framework:limiter name="username_ip_login"
    17. policy="token_bucket"
    18. limit="5"
    19. >
    20. <framework:rate interval="5 minutes"/>
    21. </framework:limiter>
    22. <framework:limiter name="ip_login"
    23. policy="sliding_window"
    24. limit="50"
    25. interval="15 minutes"
    26. />
    27. </framework:rate-limiter>
    28. </framework:config>
    29. <srv:services>
    30. <!-- our custom login rate limiter -->
    31. <srv:service id="app.login_rate_limiter"
    32. class="Symfony\Component\Security\Http\RateLimiter\DefaultLoginRateLimiter"
    33. >
    34. <!-- 1st argument is the limiter for IP -->
    35. <srv:argument type="service" id="limiter.ip_login"/>
    36. <!-- 2nd argument is the limiter for username+IP -->
    37. <srv:argument type="service" id="limiter.username_ip_login"/>
    38. </srv:service>
    39. </srv:services>
    40. <config>
    41. <firewall name="main">
    42. <!-- use a custom rate limiter via its service ID -->
    43. <login-throttling limiter="app.login_rate_limiter"/>
    44. </firewall>
    45. </config>
    46. </srv:container>
  • PHP

    1. // config/packages/security.php
    2. use Symfony\Component\DependencyInjection\ContainerBuilder;
    3. use Symfony\Component\DependencyInjection\Reference;
    4. use Symfony\Component\Security\Http\RateLimiter\DefaultLoginRateLimiter;
    5. use Symfony\Config\FrameworkConfig;
    6. use Symfony\Config\SecurityConfig;
    7. return static function (ContainerBuilder $container, FrameworkConfig $framework, SecurityConfig $security) {
    8. $framework->rateLimiter()
    9. ->limiter('username_ip_login')
    10. ->policy('token_bucket')
    11. ->limit(5)
    12. ->rate()
    13. ->interval('5 minutes')
    14. ;
    15. $framework->rateLimiter()
    16. ->limiter('ip_login')
    17. ->policy('sliding_window')
    18. ->limit(50)
    19. ->interval('15 minutes')
    20. ;
    21. $container->register('app.login_rate_limiter', DefaultLoginRateLimiter::class)
    22. ->setArguments([
    23. // 1st argument is the limiter for IP
    24. new Reference('limiter.ip_login'),
    25. // 2nd argument is the limiter for username+IP
    26. new Reference('limiter.username_ip_login'),
    27. ]);
    28. $security->firewall('main')
    29. ->loginThrottling()
    30. ->limiter('app.login_rate_limiter')
    31. ;
    32. };

4) Denying Access, Roles and other Authorization

Users can now log in to your app using your login form. 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:

  1. The user receives a specific set of roles when logging in (e.g. ROLE_ADMIN).
  2. 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.

Roles

When a user logs in, Symfony calls the getRoles() method on yourUserobject to determine which roles this user has. In theUserclass that we generated earlier, the roles are an array that’s stored in the database, and every user is *always* given at least one role:ROLE_USER`:

  1. // src/Entity/User.php
  2. // ...
  3. class User
  4. {
  5. /**
  6. * @ORM\Column(type="json")
  7. */
  8. private $roles = [];
  9. // ...
  10. public function getRoles(): array
  11. {
  12. $roles = $this->roles;
  13. // guarantee every user at least has ROLE_USER
  14. $roles[] = 'ROLE_USER';
  15. return array_unique($roles);
  16. }
  17. }

This is a nice default, but you can do whatever you want to determine which roles a user should have. Here are a few guidelines:

  • Every role must start with ROLE_ (otherwise, things won’t work as expected)
  • Other than the above rule, a role is just a string and you can invent what you need (e.g. ROLE_PRODUCT_ADMIN).

You’ll use these roles next to grant access to specific sections of your site. You can also use a role hierarchy where having some roles automatically give you other roles.

Add Code to Deny Access

There are two ways to deny access to something:

  1. access_control in security.yaml allows you to protect URL patterns (e.g. /admin/*). Simpler, but less flexible;
  2. in your controller (or other code).

Securing URL patterns (access_control)

The most basic way to secure part of your app is to secure an entire URL pattern in security.yaml. For example, to require ROLE_ADMIN for all URLs that start with /admin, you can:

  • YAML

    1. # config/packages/security.yaml
    2. security:
    3. # ...
    4. firewalls:
    5. # ...
    6. main:
    7. # ...
    8. access_control:
    9. # require ROLE_ADMIN for /admin*
    10. - { path: '^/admin', roles: ROLE_ADMIN }
    11. # or require ROLE_ADMIN or IS_AUTHENTICATED_FULLY for /admin*
    12. - { path: '^/admin', roles: [IS_AUTHENTICATED_FULLY, ROLE_ADMIN] }
    13. # the 'path' value can be any valid regular expression
    14. # (this one will match URLs like /api/post/7298 and /api/comment/528491)
    15. - { path: ^/api/(post|comment)/\d+$, roles: ROLE_USER }
  • XML

    1. <!-- config/packages/security.xml -->
    2. <?xml version="1.0" encoding="UTF-8" ?>
    3. <srv:container xmlns="http://symfony.com/schema/dic/security"
    4. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    5. xmlns:srv="http://symfony.com/schema/dic/services"
    6. xsi:schemaLocation="http://symfony.com/schema/dic/services
    7. https://symfony.com/schema/dic/services/services-1.0.xsd
    8. http://symfony.com/schema/dic/security
    9. https://symfony.com/schema/dic/security/security-1.0.xsd">
    10. <config>
    11. <!-- ... -->
    12. <firewall name="main">
    13. <!-- ... -->
    14. </firewall>
    15. <!-- require ROLE_ADMIN for /admin* -->
    16. <rule path="^/admin" role="ROLE_ADMIN"/>
    17. <!-- require ROLE_ADMIN or IS_AUTHENTICATED_FULLY for /admin* -->
    18. <rule path="^/admin">
    19. <role>ROLE_ADMIN</role>
    20. <role>IS_AUTHENTICATED_FULLY</role>
    21. </rule>
    22. <!-- the 'path' value can be any valid regular expression
    23. (this one will match URLs like /api/post/7298 and /api/comment/528491) -->
    24. <rule path="^/api/(post|comment)/\d+$" role="ROLE_USER"/>
    25. </config>
    26. </srv:container>
  • PHP

    1. // config/packages/security.php
    2. use Symfony\Config\SecurityConfig;
    3. return static function (SecurityConfig $security) {
    4. $security->enableAuthenticatorManager(true);
    5. // ...
    6. $security->firewall('main')
    7. // ...
    8. ;
    9. // require ROLE_ADMIN for /admin*
    10. $security->accessControl()
    11. ->path('^/admin')
    12. ->roles(['ROLE_ADMIN']);
    13. // require ROLE_ADMIN or IS_AUTHENTICATED_FULLY for /admin*
    14. $security->accessControl()
    15. ->path('^/admin')
    16. ->roles(['ROLE_ADMIN', 'IS_AUTHENTICATED_FULLY']);
    17. // the 'path' value can be any valid regular expression
    18. // (this one will match URLs like /api/post/7298 and /api/comment/528491)
    19. $security->accessControl()
    20. ->path('^/api/(post|comment)/\d+$')
    21. ->roles(['ROLE_USER']);
    22. };

You can define as many URL patterns as you need - each is a regular expression. BUT, only one will be matched per request: Symfony starts at the top of the list and stops when it finds the first match:

  • YAML

    1. # config/packages/security.yaml
    2. security:
    3. # ...
    4. access_control:
    5. # matches /admin/users/*
    6. - { path: '^/admin/users', roles: ROLE_SUPER_ADMIN }
    7. # matches /admin/* except for anything matching the above rule
    8. - { path: '^/admin', roles: ROLE_ADMIN }
  • XML

    1. <!-- config/packages/security.xml -->
    2. <?xml version="1.0" encoding="UTF-8" ?>
    3. <srv:container xmlns="http://symfony.com/schema/dic/security"
    4. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    5. xmlns:srv="http://symfony.com/schema/dic/services"
    6. xsi:schemaLocation="http://symfony.com/schema/dic/services
    7. https://symfony.com/schema/dic/services/services-1.0.xsd
    8. http://symfony.com/schema/dic/security
    9. https://symfony.com/schema/dic/security/security-1.0.xsd">
    10. <config>
    11. <!-- ... -->
    12. <rule path="^/admin/users" role="ROLE_SUPER_ADMIN"/>
    13. <rule path="^/admin" role="ROLE_ADMIN"/>
    14. </config>
    15. </srv:container>
  • PHP

    1. // config/packages/security.php
    2. use Symfony\Config\SecurityConfig;
    3. return static function (SecurityConfig $security) {
    4. // ...
    5. $security->accessControl()
    6. ->path('^/admin/users')
    7. ->roles(['ROLE_SUPER_ADMIN']);
    8. $security->accessControl()
    9. ->path('^/admin')
    10. ->roles(['ROLE_ADMIN']);
    11. };

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

Each access_control can also match on IP address, hostname and HTTP methods. It can also be used to redirect a user to the https version of a URL pattern. See How Does the Security access_control Work?.

Securing Controllers and other Code

You can deny access from inside a controller:

  1. // src/Controller/AdminController.php
  2. // ...
  3. public function adminDashboard(): Response
  4. {
  5. $this->denyAccessUnlessGranted('ROLE_ADMIN');
  6. // or add an optional message - seen by developers
  7. $this->denyAccessUnlessGranted('ROLE_ADMIN', null, 'User tried to access a page without having ROLE_ADMIN');
  8. }

That’s it! If access is not granted, a special Symfony\Component\Security\Core\Exception\AccessDeniedException is thrown and no more code in your controller is called. Then, one of two things will happen:

  1. If the user isn’t logged in yet, they will be asked to log in (e.g. redirected to the login page).
  2. If the user is logged in, but does not have the ROLE_ADMIN role, they’ll be shown the 403 access denied page (which you can customize).

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

  1. // src/Controller/AdminController.php
  2. // ...
  3. + use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
  4. + /**
  5. + * Require ROLE_ADMIN for *every* controller method in this class.
  6. + *
  7. + * @IsGranted("ROLE_ADMIN")
  8. + */
  9. class AdminController extends AbstractController
  10. {
  11. + /**
  12. + * Require ROLE_ADMIN for only this controller method.
  13. + *
  14. + * @IsGranted("ROLE_ADMIN")
  15. + */
  16. public function adminDashboard(): Response
  17. {
  18. // ...
  19. }
  20. }

For more information, see the FrameworkExtraBundle documentation.

Access Control in Templates

If you want to check if the current user has a certain role, you can use the built-in `is_granted() helper function in any Twig template:

  1. {% if is_granted('ROLE_ADMIN') %}
  2. <a href="...">Delete</a>
  3. {% endif %}

Securing other Services

See How to Secure any Service or Method in your Application.

Setting Individual User Permissions

Most applications require more specific access rules. For instance, a user should be able to only edit their own comments on a blog. Voters allow you to write whatever business logic you need to determine access. Using these voters is similar to the role-based access checks implemented in the previous chapters. Read How to Use Voters to Check User Permissions to learn how to implement your own voter.

Checking to see if a User is Logged In (IS_AUTHENTICATED_FULLY)

If you only want to check if a user is logged in (you don’t care about roles), you have two options. First, if you’ve given every user ROLE_USER, you can check for that role. Otherwise, you can use a special “attribute” in place of a role:

  1. // ...
  2. public function adminDashboard(): Response
  3. {
  4. $this->denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY');
  5. // ...
  6. }

You can use IS_AUTHENTICATED_FULLY anywhere roles are used: like access_control or in Twig.

IS_AUTHENTICATED_FULLY isn’t a role, but it kind of acts like one, and every user that has logged in will have this. Actually, there are some 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.
  • IS_AUTHENTICATED_ANONYMOUSLY: All users (even anonymous ones) have this - this is useful when defining a list of URLs with no access restriction - some details are in How Does the Security access_control Work?.
  • IS_ANONYMOUS: Only anonymous users are matched by this attribute.
  • IS_REMEMBERED: Only users authenticated using the remember me functionality, (i.e. a remember-me cookie).
  • IS_IMPERSONATOR: When the current user is impersonating another user in this session, this attribute will match.

New in version 5.1: The IS_ANONYMOUS, IS_REMEMBERED and IS_IMPERSONATOR attributes were introduced in Symfony 5.1.

5a) Fetching the User Object

After authentication, the User object of the current user can be accessed via the `getUser() shortcut:

  1. public function index(): Response
  2. {
  3. // usually you'll want to make sure the user is authenticated first
  4. $this->denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY');
  5. // returns your User object, or null if the user is not authenticated
  6. // use inline documentation to tell your editor your exact User class
  7. /** @var \App\Entity\User $user */
  8. $user = $this->getUser();
  9. // Call whatever methods you've added to your User class
  10. // For example, if you added a getFirstName() method, you can use that.
  11. return new Response('Well hi there '.$user->getFirstName());
  12. }

5b) Fetching the User from a Service

If you need to get the logged in user from a service, use the Symfony\Component\Security\Core\Security service:

  1. // src/Service/ExampleService.php
  2. // ...
  3. use Symfony\Component\Security\Core\Security;
  4. class ExampleService
  5. {
  6. private $security;
  7. public function __construct(Security $security)
  8. {
  9. // Avoid calling getUser() in the constructor: auth may not
  10. // be complete yet. Instead, store the entire Security object.
  11. $this->security = $security;
  12. }
  13. public function someMethod()
  14. {
  15. // returns User object or null if not authenticated
  16. $user = $this->security->getUser();
  17. // ...
  18. }
  19. }

Fetch the User in a Template

In a Twig Template the user object is available via the app.user variable thanks to the Twig global app variable:

  1. {% if is_granted('IS_AUTHENTICATED_FULLY') %}
  2. <p>Email: {{ app.user.email }}</p>
  3. {% endif %}

Logging Out

To enable logging out, activate the logout config parameter under your firewall:

  • YAML

    1. # config/packages/security.yaml
    2. security:
    3. # ...
    4. firewalls:
    5. main:
    6. # ...
    7. logout:
    8. path: app_logout
    9. # where to redirect after logout
    10. # target: app_any_route
  • XML

    1. <!-- config/packages/security.xml -->
    2. <?xml version="1.0" encoding="UTF-8" ?>
    3. <srv:container xmlns="http://symfony.com/schema/dic/security"
    4. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    5. xmlns:srv="http://symfony.com/schema/dic/services"
    6. xsi:schemaLocation="http://symfony.com/schema/dic/services
    7. https://symfony.com/schema/dic/services/services-1.0.xsd
    8. http://symfony.com/schema/dic/security
    9. https://symfony.com/schema/dic/security/security-1.0.xsd">
    10. <config>
    11. <!-- ... -->
    12. <firewall name="secured_area">
    13. <!-- ... -->
    14. <logout path="app_logout"/>
    15. </firewall>
    16. </config>
    17. </srv:container>
  • PHP

    1. // config/packages/security.php
    2. use Symfony\Config\SecurityConfig;
    3. return static function (SecurityConfig $security) {
    4. // ...
    5. $security->firewall('secured_area')
    6. // ...
    7. ->logout()
    8. ->path('app_logout');
    9. };

Next, you’ll need to create a route for this URL (but not a controller):

  • Annotations

    1. // src/Controller/SecurityController.php
    2. namespace App\Controller;
    3. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
    4. use Symfony\Component\Routing\Annotation\Route;
    5. class SecurityController extends AbstractController
    6. {
    7. /**
    8. * @Route("/logout", name="app_logout", methods={"GET"})
    9. */
    10. public function logout(): void
    11. {
    12. // controller can be blank: it will never be executed!
    13. throw new \Exception('Don\'t forget to activate logout in security.yaml');
    14. }
    15. }
  • Attributes

    1. // src/Controller/SecurityController.php
    2. namespace App\Controller;
    3. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
    4. use Symfony\Component\Routing\Annotation\Route;
    5. class SecurityController extends AbstractController
    6. {
    7. #[Route('/logout', name: 'app_logout', methods: ['GET'])]
    8. public function logout()
    9. {
    10. // controller can be blank: it will never be executed!
    11. throw new \Exception('Don\'t forget to activate logout in security.yaml');
    12. }
    13. }
  • YAML

    1. # config/routes.yaml
    2. app_logout:
    3. path: /logout
    4. methods: GET
  • XML

    1. <!-- config/routes.xml -->
    2. <?xml version="1.0" encoding="UTF-8" ?>
    3. <routes xmlns="http://symfony.com/schema/routing"
    4. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    5. xsi:schemaLocation="http://symfony.com/schema/routing
    6. https://symfony.com/schema/routing/routing-1.0.xsd">
    7. <route id="app_logout" path="/logout" methods="GET"/>
    8. </routes>
  • PHP

    1. // config/routes.php
    2. use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
    3. return function (RoutingConfigurator $routes) {
    4. $routes->add('app_logout', '/logout')
    5. ->methods(['GET'])
    6. ;
    7. };

And that’s it! By sending a user to the app_logout route (i.e. to /logout) Symfony will un-authenticate the current user and redirect them.

Customizing Logout

New in version 5.1: The LogoutEvent was introduced in Symfony 5.1. Prior to this version, you had to use a logout success handler to customize the logout.

In some cases you need to execute extra logic upon logout (e.g. invalidate some tokens) or want to customize what happens after a logout. During logout, a Symfony\Component\Security\Http\Event\LogoutEvent is dispatched. Register an event listener or subscriber to execute custom logic. The following information is available in the event class:

`getToken()

Returns the security token of the session that is about to be logged out.

`getRequest()

Returns the current request.

`getResponse()

Returns a response, if it is already set by a custom listener. Use `setResponse() to configure a custom logout response.

Tip

Every Security firewall has its own event dispatcher (security.event_dispatcher.FIREWALLNAME). The logout event is dispatched on both the global and firewall dispatcher. You can register on the firewall dispatcher if you want your listener to only be executed for a specific firewall. For instance, if you have an api and main firewall, use this configuration to register only on the logout event in the main firewall:

  • YAML

    1. # config/services.yaml
    2. services:
    3. # ...
    4. App\EventListener\CustomLogoutSubscriber:
    5. tags:
    6. - name: kernel.event_subscriber
    7. dispatcher: security.event_dispatcher.main
  • XML

    1. <!-- config/services.xml -->
    2. <?xml version="1.0" encoding="UTF-8" ?>
    3. <container xmlns="http://symfony.com/schema/dic/services"
    4. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    5. xsi:schemaLocation="http://symfony.com/schema/dic/services
    6. https://symfony.com/schema/dic/services/services-1.0.xsd">
    7. <services>
    8. <!-- ... -->
    9. <service id="App\EventListener\CustomLogoutSubscriber">
    10. <tag name="kernel.event_subscriber"
    11. dispacher="security.event_dispatcher.main"
    12. />
    13. </service>
    14. </services>
    15. </container>
  • PHP

    1. // config/services.php
    2. namespace Symfony\Component\DependencyInjection\Loader\Configurator;
    3. use App\EventListener\CustomLogoutListener;
    4. use App\EventListener\CustomLogoutSubscriber;
    5. use Symfony\Component\Security\Http\Event\LogoutEvent;
    6. return function(ContainerConfigurator $configurator) {
    7. $services = $configurator->services();
    8. $services->set(CustomLogoutSubscriber::class)
    9. ->tag('kernel.event_subscriber', [
    10. 'dispatcher' => 'security.event_dispatcher.main',
    11. ]);
    12. };

Hierarchical Roles

Instead of giving many roles to each user, you can define role inheritance rules by creating a role hierarchy:

  • YAML

    1. # config/packages/security.yaml
    2. security:
    3. # ...
    4. role_hierarchy:
    5. ROLE_ADMIN: ROLE_USER
    6. ROLE_SUPER_ADMIN: [ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]
  • XML

    1. <!-- config/packages/security.xml -->
    2. <?xml version="1.0" encoding="UTF-8" ?>
    3. <srv:container xmlns="http://symfony.com/schema/dic/security"
    4. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    5. xmlns:srv="http://symfony.com/schema/dic/services"
    6. xsi:schemaLocation="http://symfony.com/schema/dic/services
    7. https://symfony.com/schema/dic/services/services-1.0.xsd
    8. http://symfony.com/schema/dic/security
    9. https://symfony.com/schema/dic/security/security-1.0.xsd">
    10. <config>
    11. <!-- ... -->
    12. <role id="ROLE_ADMIN">ROLE_USER</role>
    13. <role id="ROLE_SUPER_ADMIN">ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH</role>
    14. </config>
    15. </srv:container>
  • PHP

    1. // config/packages/security.php
    2. use Symfony\Config\SecurityConfig;
    3. return static function (SecurityConfig $security) {
    4. // ...
    5. $security->roleHierarchy('ROLE_ADMIN', ['ROLE_USER']);
    6. $security->roleHierarchy('ROLE_SUPER_ADMIN', ['ROLE_ADMIN', 'ROLE_ALLOWED_TO_SWITCH']);
    7. };

Users with the ROLE_ADMIN role will also have the ROLE_USER role. And users with ROLE_SUPER_ADMIN, will automatically have ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH and ROLE_USER (inherited from ROLE_ADMIN).

For role hierarchy to work, do not try to call `$user->getRoles() manually. For example, in a controller extending from the base controller:

  1. // BAD - $user->getRoles() will not know about the role hierarchy
  2. $hasAccess = in_array('ROLE_ADMIN', $user->getRoles());
  3. // GOOD - use of the normal security methods
  4. $hasAccess = $this->isGranted('ROLE_ADMIN');
  5. $this->denyAccessUnlessGranted('ROLE_ADMIN');

Note

The role_hierarchy values are static - you can’t, for example, store the role hierarchy in a database. If you need that, create a custom security voter that looks for the user roles in the database.

Frequently Asked Questions

Can I have Multiple Firewalls?

Yes! But it’s usually not necessary. Each firewall is like a separate security system. And so, unless you have very different authentication needs, one firewall usually works well. With Guard authentication, you can create various, diverse ways of allowing authentication (e.g. form login, API key authentication and LDAP) all under the same firewall.

Can I Share Authentication Between Firewalls?

Yes, but only with some configuration. If you’re using multiple firewalls and you authenticate against one firewall, you will not be authenticated against any other firewalls automatically. Different firewalls are like different security systems. To do this you have to explicitly specify the same Firewall Context for different firewalls. But usually for most applications, having one main firewall is enough.

Security doesn’t seem to work on my Error Pages

As routing is done before security, 404 error pages are not covered by any firewall. This means you can’t check for security or even access the user object on these pages. See How to Customize Error Pages for more details.

My Authentication Doesn’t Seem to Work: No Errors, but I’m Never Logged In

Sometimes authentication may be successful, but after redirecting, you’re logged out immediately due to a problem loading the User from the session. To see if this is an issue, check your log file (var/log/dev.log) for the log message:

Cannot refresh token because user has changed

If you see this, there are two possible causes. First, there may be a problem loading your User from the session. See Understanding how Users are Refreshed from the Session. Second, if certain user information was changed in the database since the last page refresh, Symfony will purposely log out the user for security reasons.

Learn More

Authentication (Identifying/Logging in the User)

Authorization (Denying Access)

This work, including the code samples, is licensed under a Creative Commons BY-SA 3.0 license.