diff --git a/configuration/external_parameters.rst b/configuration/external_parameters.rst
index 579bc168a75..f3cf284a784 100644
--- a/configuration/external_parameters.rst
+++ b/configuration/external_parameters.rst
@@ -286,6 +286,7 @@ Symfony provides the following env var processors:
# config/packages/security.yaml
parameters:
env(HEALTH_CHECK_METHOD): 'Symfony\Component\HttpFoundation\Request::METHOD_HEAD'
+
security:
access_control:
- { path: '^/health-check$', methods: '%env(const:HEALTH_CHECK_METHOD)%' }
diff --git a/security.rst b/security.rst
index 4443a7ef33a..034390b00d3 100644
--- a/security.rst
+++ b/security.rst
@@ -226,7 +226,7 @@ user to be logged in to access this URL:
access_control:
# require ROLE_ADMIN for /admin*
- - { path: ^/admin, roles: ROLE_ADMIN }
+ - { path: '^/admin', roles: ROLE_ADMIN }
.. code-block:: xml
@@ -713,7 +713,7 @@ URL pattern. You saw this earlier, where anything matching the regular expressio
access_control:
# require ROLE_ADMIN for /admin*
- - { path: ^/admin, roles: ROLE_ADMIN }
+ - { path: '^/admin', roles: ROLE_ADMIN }
.. code-block:: xml
@@ -751,7 +751,7 @@ URL pattern. You saw this earlier, where anything matching the regular expressio
],
'access_control' => [
// require ROLE_ADMIN for /admin*
- ['path' => '^/admin', 'role' => 'ROLE_ADMIN'],
+ ['path' => '^/admin', 'roles' => 'ROLE_ADMIN'],
],
]);
@@ -773,8 +773,8 @@ matches the URL.
# ...
access_control:
- - { path: ^/admin/users, roles: ROLE_SUPER_ADMIN }
- - { path: ^/admin, roles: ROLE_ADMIN }
+ - { path: '^/admin/users', roles: ROLE_SUPER_ADMIN }
+ - { path: '^/admin', roles: ROLE_ADMIN }
.. code-block:: xml
@@ -801,8 +801,8 @@ matches the URL.
// ...
'access_control' => [
- ['path' => '^/admin/users', 'role' => 'ROLE_SUPER_ADMIN'],
- ['path' => '^/admin', 'role' => 'ROLE_ADMIN'],
+ ['path' => '^/admin/users', 'roles' => 'ROLE_SUPER_ADMIN'],
+ ['path' => '^/admin', 'roles' => 'ROLE_ADMIN'],
],
]);
diff --git a/security/access_control.rst b/security/access_control.rst
index d639e56b611..a3c3e57e3a6 100644
--- a/security/access_control.rst
+++ b/security/access_control.rst
@@ -22,10 +22,10 @@ for each ``access_control`` entry, which determines whether or not a given
access control should be used on this request. The following ``access_control``
options are used for matching:
-* ``path``
-* ``ip`` or ``ips`` (netmasks are also supported)
-* ``host``
-* ``methods``
+* ``path``: a regular expression (without delimiters)
+* ``ip`` or ``ips``: netmasks are also supported
+* ``host``: a regular expression
+* ``methods``: one or many methods
Take the following ``access_control`` entries as an example:
@@ -37,10 +37,11 @@ Take the following ``access_control`` entries as an example:
security:
# ...
access_control:
- - { path: ^/admin, roles: ROLE_USER_IP, ip: 127.0.0.1 }
- - { path: ^/admin, roles: ROLE_USER_HOST, host: symfony\.com$ }
- - { path: ^/admin, roles: ROLE_USER_METHOD, methods: [POST, PUT] }
- - { path: ^/admin, roles: ROLE_USER }
+ - { path: '^/admin', roles: ROLE_USER_IP, ip: 127.0.0.1 }
+ - { path: '^/admin', roles: ROLE_USER_HOST, host: symfony\.com$ }
+ - { path: '^/admin', roles: ROLE_USER_METHOD, methods: [POST, PUT] }
+ # Defining many roles means we need one of them (like an OR condition)
+ - { path: '^/admin', roles: [ROLE_MANAGER, ROLE_ADMIN] }
.. code-block:: xml
@@ -57,7 +58,8 @@ Take the following ``access_control`` entries as an example:
-
+
+
@@ -69,22 +71,23 @@ Take the following ``access_control`` entries as an example:
'access_control' => [
[
'path' => '^/admin',
- 'role' => 'ROLE_USER_IP',
- 'ip' => '127.0.0.1',
+ 'roles' => 'ROLE_USER_IP',
+ 'ips' => '127.0.0.1',
],
[
'path' => '^/admin',
- 'role' => 'ROLE_USER_HOST',
+ 'roles' => 'ROLE_USER_HOST',
'host' => 'symfony\.com$',
],
[
'path' => '^/admin',
- 'role' => 'ROLE_USER_METHOD',
+ 'roles' => 'ROLE_USER_METHOD',
'methods' => 'POST, PUT',
],
[
'path' => '^/admin',
- 'role' => 'ROLE_USER',
+ // Defining many roles, mean we need one of them (like an OR condition)
+ 'roles' => ['ROLE_MANAGER', 'ROLE_ADMIN'],
],
],
]);
@@ -92,8 +95,8 @@ Take the following ``access_control`` entries as an example:
For each incoming request, Symfony will decide which ``access_control``
to use based on the URI, the client's IP address, the incoming host name,
and the request method. Remember, the first rule that matches is used, and
-if ``ip``, ``host`` or ``method`` are not specified for an entry, that ``access_control``
-will match any ``ip``, ``host`` or ``method``:
+if ``ips``, ``host`` or ``methods`` are not specified for an entry, that
+``access_control`` will match any ``ips``, ``host`` or ``methods``:
+-----------------+-------------+-------------+------------+--------------------------------+-------------------------------------------------------------+
| URI | IP | HOST | METHOD | ``access_control`` | Why? |
@@ -114,9 +117,10 @@ will match any ``ip``, ``host`` or ``method``:
| ``/admin/user`` | 168.0.0.1 | example.com | POST | rule #3 (``ROLE_USER_METHOD``) | The ``ip`` and ``host`` don't match the first two entries, |
| | | | | | but the third - ``ROLE_USER_METHOD`` - matches and is used. |
+-----------------+-------------+-------------+------------+--------------------------------+-------------------------------------------------------------+
-| ``/admin/user`` | 168.0.0.1 | example.com | GET | rule #4 (``ROLE_USER``) | The ``ip``, ``host`` and ``method`` prevent the first |
+| ``/admin/user`` | 168.0.0.1 | example.com | GET | rule #4 (``ROLE_MANAGER``) | The ``ip``, ``host`` and ``method`` prevent the first |
| | | | | | three entries from matching. But since the URI matches the |
-| | | | | | ``path`` pattern of the ``ROLE_USER`` entry, it is used. |
+| | | | | | ``path`` pattern, then the ``ROLE_MANAGER`` (or the |
+| | | | | | ``ROLE_ADMIN``) is used. |
+-----------------+-------------+-------------+------------+--------------------------------+-------------------------------------------------------------+
| ``/foo`` | 127.0.0.1 | symfony.com | POST | matches no entries | This doesn't match any ``access_control`` rules, since its |
| | | | | | URI doesn't match any of the ``path`` values. |
@@ -144,6 +148,14 @@ options:
does not match this value (e.g. ``https``), the user will be redirected
(e.g. redirected from ``http`` to ``https``, or vice versa).
+.. tip::
+
+ Behind the scenes, the array value of ``roles`` is passed as the
+ ``$attributes`` argument to each voter in the application with the
+ :class:`Symfony\\Component\\HttpFoundation\\Request` as ``$subject``. You
+ can learn how to use your custom attributes by reading
+ :ref:`security/custom-voter`.
+
.. tip::
If access is denied, the system will try to authenticate the user if not
@@ -180,8 +192,8 @@ pattern so that it is only accessible by requests from the local server itself:
access_control:
#
# the 'ips' option supports IP addresses and subnet masks
- - { path: ^/internal, roles: IS_AUTHENTICATED_ANONYMOUSLY, ips: [127.0.0.1, ::1, 192.168.0.1/24] }
- - { path: ^/internal, roles: ROLE_NO_ACCESS }
+ - { path: '^/internal', roles: IS_AUTHENTICATED_ANONYMOUSLY, ips: [127.0.0.1, ::1, 192.168.0.1/24] }
+ - { path: '^/internal', roles: ROLE_NO_ACCESS }
.. code-block:: xml
@@ -214,13 +226,13 @@ pattern so that it is only accessible by requests from the local server itself:
'access_control' => [
[
'path' => '^/internal',
- 'role' => 'IS_AUTHENTICATED_ANONYMOUSLY',
+ 'roles' => 'IS_AUTHENTICATED_ANONYMOUSLY',
// the 'ips' option supports IP addresses and subnet masks
'ips' => ['127.0.0.1', '::1'],
],
[
'path' => '^/internal',
- 'role' => 'ROLE_NO_ACCESS',
+ 'roles' => 'ROLE_NO_ACCESS',
],
],
]);
@@ -265,7 +277,9 @@ key:
access_control:
-
path: ^/_internal/secure
- allow_if: "'127.0.0.1' == request.getClientIp() or has_role('ROLE_ADMIN')"
+ roles: 'ROLE_ADMIN'
+ // with this expression, we need it to be true OR a ROLE_ADMIN
+ allow_if: "'127.0.0.1' == request.getClientIp() or request.header.has('X-Secure-Access')"
.. code-block:: xml
@@ -279,7 +293,9 @@ key:
+ role="ROLE_ADMIN"
+
+ allow-if="'127.0.0.1' == request.getClientIp() or request.header.has('X-Secure-Access')" />
@@ -288,13 +304,27 @@ key:
'access_control' => [
[
'path' => '^/_internal/secure',
- 'allow_if' => '"127.0.0.1" == request.getClientIp() or has_role("ROLE_ADMIN")',
+ 'roles' => 'ROLE_ADMIN',
+ // with this expression, we need it to be true OR a ROLE_ADMIN
+ 'allow_if' => '"127.0.0.1" == request.getClientIp() or request.header.has('X-Secure-Access')',
],
],
-In this case, when the user tries to access any URL starting with ``/_internal/secure``,
-they will only be granted access if the IP address is ``127.0.0.1`` or if
-the user has the ``ROLE_ADMIN`` role.
+In this case, when the user tries to access any URL starting with
+``/_internal/secure``, they will only be granted access if the IP address is
+``127.0.0.1`` or a secure header, or if the user has the ``ROLE_ADMIN`` role.
+
+.. note::
+
+ Internally ``allow_if`` triggers the built-in
+ :class:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\ExpressionVoter`
+ as like it was part of the attributes defined in the ``roles`` option.
+ By default, access is granted if one of the attribute is granted. In other
+ words, it means ``allow_if`` can grant access even if the
+ :class:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\RoleVoter` or
+ any other voter denied it.
+ To change this behavior, you may need to learn more about
+ :ref:`access decision strategy `.
Inside the expression, you have access to a number of different variables
and functions including ``request``, which is the Symfony
@@ -345,7 +375,7 @@ the user will be redirected to ``https``:
'access_control' => [
[
'path' => '^/cart/checkout',
- 'role' => 'IS_AUTHENTICATED_ANONYMOUSLY',
+ 'roles' => 'IS_AUTHENTICATED_ANONYMOUSLY',
'requires_channel' => 'https',
],
],
diff --git a/security/api_key_authentication.rst b/security/api_key_authentication.rst
index b0761402e68..dc8394caa55 100644
--- a/security/api_key_authentication.rst
+++ b/security/api_key_authentication.rst
@@ -369,7 +369,7 @@ If you have defined ``access_control``, make sure to add a new entry:
# ...
access_control:
- - { path: ^/api, roles: ROLE_API }
+ - { path: '^/api', roles: ROLE_API }
.. code-block:: xml
@@ -392,7 +392,7 @@ If you have defined ``access_control``, make sure to add a new entry:
'access_control' => [
[
'path' => '^/api',
- 'role' => 'ROLE_API',
+ 'roles' => 'ROLE_API',
],
],
]);
diff --git a/security/force_https.rst b/security/force_https.rst
index dd8e4fc1b3b..5876c6a5f18 100644
--- a/security/force_https.rst
+++ b/security/force_https.rst
@@ -18,7 +18,7 @@ to use HTTPS then you could use the following configuration:
# ...
access_control:
- - { path: ^/secure, roles: ROLE_ADMIN, requires_channel: https }
+ - { path: '^/secure', roles: ROLE_ADMIN, requires_channel: https }
.. code-block:: xml
@@ -46,7 +46,7 @@ to use HTTPS then you could use the following configuration:
'access_control' => [
[
'path' => '^/secure',
- 'role' => 'ROLE_ADMIN',
+ 'roles' => 'ROLE_ADMIN',
'requires_channel' => 'https',
],
],
@@ -66,7 +66,7 @@ role:
# ...
access_control:
- - { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY, requires_channel: https }
+ - { path: '^/login', roles: IS_AUTHENTICATED_ANONYMOUSLY, requires_channel: https }
.. code-block:: xml
@@ -97,7 +97,7 @@ role:
'access_control' => [
[
'path' => '^/login',
- 'role' => 'IS_AUTHENTICATED_ANONYMOUSLY',
+ 'roles' => 'IS_AUTHENTICATED_ANONYMOUSLY',
'requires_channel' => 'https',
],
],
diff --git a/security/form_login_setup.rst b/security/form_login_setup.rst
index 527ffa120f2..0f5f02c261c 100644
--- a/security/form_login_setup.rst
+++ b/security/form_login_setup.rst
@@ -283,7 +283,7 @@ all URLs (including the ``/login`` URL), will cause a redirect loop:
# ...
access_control:
- - { path: ^/, roles: ROLE_ADMIN }
+ - { path: '^/', roles: ROLE_ADMIN }
.. code-block:: xml
@@ -307,7 +307,7 @@ all URLs (including the ``/login`` URL), will cause a redirect loop:
// ...
'access_control' => [
- ['path' => '^/', 'role' => 'ROLE_ADMIN'],
+ ['path' => '^/', 'roles' => 'ROLE_ADMIN'],
],
Adding an access control that matches ``/login/*`` and requires *no* authentication
@@ -321,8 +321,8 @@ fixes the problem:
# ...
access_control:
- - { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- - { path: ^/, roles: ROLE_ADMIN }
+ - { path: '^/login', roles: IS_AUTHENTICATED_ANONYMOUSLY }
+ - { path: '^/', roles: ROLE_ADMIN }
.. code-block:: xml
@@ -347,8 +347,8 @@ fixes the problem:
// ...
'access_control' => [
- ['path' => '^/login', 'role' => 'IS_AUTHENTICATED_ANONYMOUSLY'],
- ['path' => '^/', 'role' => 'ROLE_ADMIN'],
+ ['path' => '^/login', 'roles' => 'IS_AUTHENTICATED_ANONYMOUSLY'],
+ ['path' => '^/', 'roles' => 'ROLE_ADMIN'],
],
3. Be Sure check_path Is Behind a Firewall
diff --git a/security/multiple_guard_authenticators.rst b/security/multiple_guard_authenticators.rst
index 7816f6030d3..185dedebc51 100644
--- a/security/multiple_guard_authenticators.rst
+++ b/security/multiple_guard_authenticators.rst
@@ -108,9 +108,9 @@ the solution is to split the configuration into two separate firewalls:
authenticators:
- AppBundle\Security\LoginFormAuthenticator
access_control:
- - { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- - { path: ^/api, roles: ROLE_API_USER }
- - { path: ^/, roles: ROLE_USER }
+ - { path: '^/login', roles: IS_AUTHENTICATED_ANONYMOUSLY }
+ - { path: '^/api', roles: ROLE_API_USER }
+ - { path: '^/', roles: ROLE_USER }
.. code-block:: xml
@@ -168,8 +168,8 @@ the solution is to split the configuration into two separate firewalls:
],
],
'access_control' => [
- ['path' => '^/login', 'role' => 'IS_AUTHENTICATED_ANONYMOUSLY'],
- ['path' => '^/api', 'role' => 'ROLE_API_USER'],
- ['path' => '^/', 'role' => 'ROLE_USER'],
+ ['path' => '^/login', 'roles' => 'IS_AUTHENTICATED_ANONYMOUSLY'],
+ ['path' => '^/api', 'roles' => 'ROLE_API_USER'],
+ ['path' => '^/', 'roles' => 'ROLE_USER'],
],
]);
diff --git a/security/voters.rst b/security/voters.rst
index f557ef07bd4..532a6eccb8b 100644
--- a/security/voters.rst
+++ b/security/voters.rst
@@ -1,6 +1,8 @@
.. index::
single: Security; Data Permission Voters
+.. _security/custom-voter:
+
How to Use Voters to Check User Permissions
===========================================
@@ -19,7 +21,8 @@ How Symfony Uses Voters
In order to use voters, you have to understand how Symfony works with them.
All voters are called each time you use the ``isGranted()`` method on Symfony's
authorization checker or call ``denyAccessUnlessGranted`` in a controller (which
-uses the authorization checker).
+uses the authorization checker), or by
+:ref:`access controls `.
Ultimately, Symfony takes the responses from all voters and makes the final
decision (to allow or deny access to the resource) according to the strategy defined