diff --git a/security.rst b/security.rst
index 584db4b78b5..15a56b5b245 100644
--- a/security.rst
+++ b/security.rst
@@ -37,6 +37,49 @@ install the security feature before using it:
$ composer require symfony/security-bundle
+
+.. tip::
+
+ A :doc:`new experimental Security `
+ was introduced in Symfony 5.1, which will eventually 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:
+
+ .. configuration-block::
+
+ .. code-block:: yaml
+
+ # config/packages/security.yaml
+ security:
+ enable_authenticator_manager: true
+ # ...
+
+ .. code-block:: xml
+
+
+
+
+
+
+
+
+
+
+ .. code-block:: php
+
+ // config/packages/security.php
+ $container->loadFromExtension('security', [
+ 'enable_authenticator_manager' => true,
+ // ...
+ ]);
+
.. _initial-security-yml-setup-authentication:
.. _initial-security-yaml-setup-authentication:
.. _create-user-class:
@@ -1121,6 +1164,7 @@ Authentication (Identifying/Logging in the User)
.. toctree::
:maxdepth: 1
+ security/experimental_authenticators
security/form_login_setup
security/reset_password
security/json_login_setup
diff --git a/security/experimental_authenticators.rst b/security/experimental_authenticators.rst
new file mode 100644
index 00000000000..4299a452dcf
--- /dev/null
+++ b/security/experimental_authenticators.rst
@@ -0,0 +1,499 @@
+Using the new Authenticator-based Security
+==========================================
+
+.. versionadded:: 5.1
+
+ Authenticator-based security was introduced as an
+ :doc:`experimental feature ` in
+ Symfony 5.1.
+
+In Symfony 5.1, a new authentication system was introduced. This system
+changes the internals of Symfony Security, to make it more extensible
+and more understandable.
+
+Enabling the System
+-------------------
+
+The authenticator-based system can be enabled using the
+``enable_authenticator_manager`` setting:
+
+.. configuration-block::
+
+ .. code-block:: yaml
+
+ # config/packages/security.yaml
+ security:
+ enable_authenticator_manager: true
+ # ...
+
+ .. code-block:: xml
+
+
+
+
+
+
+
+
+
+
+ .. code-block:: php
+
+ // config/packages/security.php
+ $container->loadFromExtension('security', [
+ 'enable_authenticator_manager' => true,
+ // ...
+ ]);
+
+The new system is backwards compatible with the current authentication
+system, with some exceptions that will be explained in this article:
+
+* :ref:`Anonymous users no longer exist `
+* :ref:`Configuring the authentication entry point is required when more than one authenticator is used `
+* :ref:`The authentication providers are refactored into Authenticators `
+
+.. _authenticators-removed-anonymous:
+
+Adding Support for Unsecured Access (i.e. Anonymous Users)
+----------------------------------------------------------
+
+In Symfony, visitors that haven't yet logged in to your website were called
+:ref:`anonymous users `. The new system no longer
+has anonymous authentication. Instead, these sessions are now treated as
+unauthenticated (i.e. there is no security token). When using
+``isGranted()``, the result will always be ``false`` (i.e. denied) as this
+session is handled as a user without any privileges.
+
+In the ``access_control`` configuration, you can use the new
+``PUBLIC_ACCESS`` security attribute to whitelist some routes for
+unauthenticated access (e.g. the login page):
+
+.. configuration-block::
+
+ .. code-block:: yaml
+
+ # config/packages/security.yaml
+ security:
+ enable_authenticator_manager: true
+
+ # ...
+ access_control:
+ # allow unauthenticated users to access the login form
+ - { path: ^/admin/login, roles: PUBLIC_ACCESS }
+
+ # but require authentication for all other admin routes
+ - { path: ^/admin, roles: ROLE_ADMIN }
+
+ .. code-block:: xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ .. code-block:: php
+
+ // config/packages/security.php
+ use Symfony\Component\Security\Http\Firewall\AccessListener;
+
+ $container->loadFromExtension('security', [
+ 'enable_authenticator_manager' => true,
+
+ // ...
+ 'access_control' => [
+ // allow unauthenticated users to access the login form
+ ['path' => '^/admin/login', 'roles' => AccessListener::PUBLIC_ACCESS],
+
+ // but require authentication for all other admin routes
+ ['path' => '^/admin', 'roles' => 'ROLE_ADMIN'],
+ ],
+ ]);
+
+.. _authenticators-required-entry-point:
+
+Configuring the Authentication Entry Point
+------------------------------------------
+
+Sometimes, one firewall has multiple ways to authenticate (e.g. both a form
+login and an API token authentication). In these cases, it is now required
+to configure the *authentication entry point*. The entry point is used to
+generate a response when the user is not yet authenticated but tries to access
+a page that requires authentication. This can be used for instance to redirect
+the user to the login page.
+
+You can configure this using the ``entry_point`` setting:
+
+.. configuration-block::
+
+ .. code-block:: yaml
+
+ # config/packages/security.yaml
+ security:
+ enable_authenticator_manager: true
+
+ # ...
+ firewalls:
+ main:
+ # allow authentication using a form or HTTP basic
+ form_login: ~
+ http_basic: ~
+
+ # configure the form authentication as the entry point for unauthenticated users
+ entry_point: form_login
+
+ .. code-block:: xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ .. code-block:: php
+
+ // config/packages/security.php
+ use Symfony\Component\Security\Http\Firewall\AccessListener;
+
+ $container->loadFromExtension('security', [
+ 'enable_authenticator_manager' => true,
+
+ // ...
+ 'firewalls' => [
+ 'main' => [
+ // allow authentication using a form or HTTP basic
+ 'form_login' => null,
+ 'http_basic' => null,
+
+ // configure the form authentication as the entry point for unauthenticated users
+ 'entry_point' => 'form_login'
+ ],
+ ],
+ ]);
+
+.. note::
+
+ You can also create your own authentication entry point by creating a
+ class that implements
+ :class:`Symfony\\Component\\Security\\Http\\EntryPoint\\AuthenticationEntryPointInterface`.
+ You can then set ``entry_point`` to the service id (e.g.
+ ``entry_point: App\Security\CustomEntryPoint``)
+
+.. _authenticators-removed-authentication-providers:
+
+Creating a Custom Authenticator
+-------------------------------
+
+Security traditionally could be extended by writing
+:doc:`custom authentication providers `.
+The authenticator-based system dropped support for these providers and
+introduced a new authenticator interface as a base for custom
+authentication methods.
+
+.. tip::
+
+ :doc:`Guard authenticators ` are still
+ supported in the authenticator-based system. It is however recommended
+ to also update these when you're refactoring your application to the
+ new system. The new authenticator interface has many similarities with the
+ guard authenticator interface, making the rewrite easier.
+
+Authenticators should implement the
+:class:`Symfony\\Component\\Security\\Http\\Authenticator\\AuthenticatorInterface`.
+You can also extend
+:class:`Symfony\\Component\\Security\\Http\\Authenticator\\AbstractAuthenticator`,
+which has a default implementation for the ``createAuthenticatedToken()``
+method that fits most use-cases::
+
+ // src/Security/ApiKeyAuthenticator.php
+ namespace App\Security;
+
+ use App\Entity\User;
+ use Doctrine\ORM\EntityManagerInterface;
+ use Symfony\Component\HttpFoundation\JsonResponse;
+ use Symfony\Component\HttpFoundation\Request;
+ use Symfony\Component\HttpFoundation\Response;
+ use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
+ use Symfony\Component\Security\Core\Exception\AuthenticationException;
+ use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException;
+ use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
+ use Symfony\Component\Security\Http\Authenticator\AbstractAuthenticator;
+ use Symfony\Component\Security\Http\Authenticator\Passport\PassportInterface;
+ use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;
+
+ class ApiKeyAuthenticator extends AbstractAuthenticator
+ {
+ private $entityManager;
+
+ public function __construct(EntityManagerInterface $entityManager)
+ {
+ $this->entityManager = $entityManager;
+ }
+
+ /**
+ * Called on every request to decide if this authenticator should be
+ * used for the request. Returning `false` will cause this authenticator
+ * to be skipped.
+ */
+ public function supports(Request $request): ?bool
+ {
+ return $request->headers->has('X-AUTH-TOKEN');
+ }
+
+ public function authenticate(Request $request): PassportInterface
+ {
+ $apiToken = $request->headers->get('X-AUTH-TOKEN');
+ if (null === $apiToken) {
+ // The token header was empty, authentication fails with HTTP Status
+ // Code 401 "Unauthorized"
+ throw new CustomUserMessageAuthenticationException('No API token provided');
+ }
+
+ $user = $this->entityManager->getRepository(User::class)
+ ->findOneBy(['apiToken' => $apiToken])
+ ;
+ if (null === $user) {
+ throw new UsernameNotFoundException();
+ }
+
+ return new SelfValidatingPassport($user);
+ }
+
+ public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
+ {
+ // on success, let the request continue
+ return null;
+ }
+
+ public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response
+ {
+ $data = [
+ // you may want to customize or obfuscate the message first
+ 'message' => strtr($exception->getMessageKey(), $exception->getMessageData())
+
+ // or to translate this message
+ // $this->translator->trans($exception->getMessageKey(), $exception->getMessageData())
+ ];
+
+ return new JsonResponse($data, Response::HTTP_UNAUTHORIZED);
+ }
+ }
+
+The authenticator can be enabled using the ``custom_authenticators`` setting:
+
+.. configuration-block::
+
+ .. code-block:: yaml
+
+ # config/packages/security.yaml
+ security:
+ enable_authenticator_manager: true
+
+ # ...
+ firewalls:
+ main:
+ custom_authenticators:
+ - App\Security\ApiKeyAuthenticator
+
+ # don't forget to also configure the entry_point if the
+ # authenticator implements AuthenticatorEntryPointInterface
+ # entry_point: App\Security\CustomFormLoginAuthenticator
+
+ .. code-block:: xml
+
+
+
+
+
+
+
+
+
+
+
+ App\Security\ApiKeyAuthenticator
+
+
+
+
+ .. code-block:: php
+
+ // config/packages/security.php
+ use App\Security\ApiKeyAuthenticator;
+ use Symfony\Component\Security\Http\Firewall\AccessListener;
+
+ $container->loadFromExtension('security', [
+ 'enable_authenticator_manager' => true,
+
+ // ...
+ 'firewalls' => [
+ 'main' => [
+ 'custom_authenticators' => [
+ ApiKeyAuthenticator::class,
+ ],
+
+ // don't forget to also configure the entry_point if the
+ // authenticator implements AuthenticatorEntryPointInterface
+ // 'entry_point' => [App\Security\CustomFormLoginAuthenticator::class],
+ ],
+ ],
+ ]);
+
+The ``authenticate()`` method is the most important method of the
+authenticator. Its job is to extract credentials (e.g. username &
+password, or API tokens) from the ``Request`` object and transform these
+into a security
+:class:`Symfony\\Component\\Security\\Http\\Authenticator\\Passport\\Passport`.
+
+.. tip::
+
+ If you want to customize the login form, you can also extend from the
+ :class:`Symfony\\Component\\Security\\Http\\Authenticator\\AbstractLoginFormAuthenticator`
+ class instead.
+
+Security Passports
+~~~~~~~~~~~~~~~~~~
+
+A passport is an object that contains the user that will be authenticated as
+well as other pieces of information, like whether a password should be checked
+or if "remember me" functionality should be enabled.
+
+The default
+:class:`Symfony\\Component\\Security\\Http\\Authenticator\\Passport\\Passport`.
+requires a user object and credentials. The following credential classes
+are supported by default:
+
+
+:class:`Symfony\\Component\\Security\\Http\\Authenticator\\Passport\\Credentials\\PasswordCredentials`
+ This requires a plaintext ``$password``, which is validated using the
+ :ref:`password encoder configured for the user `.
+
+:class:`Symfony\\Component\\Security\\Http\\Authenticator\\Passport\\Credentials\\CustomCredentials`
+ Allows a custom closure to check credentials::
+
+ // ...
+ return new Passport($user, new CustomCredentials(
+ // If this function returns anything else than `true`, the credentials
+ // are marked as invalid.
+ // The $credentials parameter is equal to the next argument of this class
+ function ($credentials, UserInterface $user) {
+ return $user->getApiToken() === $credentials;
+ },
+
+ // The custom credentials
+ $apiToken
+ ));
+
+.. note::
+
+ If you don't need any credentials to be checked (e.g. a JWT token), you
+ can use the
+ :class:`Symfony\\Component\\Security\\Http\\Authenticator\\Passport\\SelfValidatingPassport`.
+ This class only requires a user and optionally `Passport Badges`_.
+
+Passport Badges
+~~~~~~~~~~~~~~~
+
+The ``Passport`` also optionally allows you to add *security badges*.
+Badges attach more data to the passport (to extend security). By default,
+the following badges are supported:
+
+:class:`Symfony\\Component\\Security\\Http\\Authenticator\\Passport\\Badge\\RememberMeBadge`
+ When this badge is added to the passport, the authenticator indicates
+ remember me is supported. Whether remember me is actually used depends
+ on special ``remember_me`` configuration. Read
+ :doc:`/security/remember_me` for more information.
+
+:class:`Symfony\\Component\\Security\\Http\\Authenticator\\Passport\\Badge\\PasswordUpgradeBadge`
+ This is used to automatically upgrade the password to a new hash upon
+ successful login. This badge requires the plaintext password and a
+ password upgrader (e.g. the user repository). See :doc:`/security/password_migration`.
+
+:class:`Symfony\\Component\\Security\\Http\\Authenticator\\Passport\\Badge\\CsrfTokenBadge`
+ Automatically validates CSRF tokens for this authenticator during
+ authentication. The constructor requires a token ID (unique per form)
+ and CSRF token (unique per request). See :doc:`/security/csrf`.
+
+:class:`Symfony\\Component\\Security\\Http\\Authenticator\\Passport\\Badge\\PreAuthenticatedBadge`
+ Indicates that this user was pre-authenticated (i.e. before Symfony was
+ initiated). This skips the
+ :doc:`pre-authentication user checker `.
+
+For instance, if you want to add CSRF and password migration to your custom
+authenticator, you would initialize the passport like this::
+
+ // ...
+ use Symfony\Component\Security\Http\Authenticator\AbstractAuthenticator;
+ use Symfony\Component\Security\Http\Authenticator\Passport\Badge\CsrfTokenBadge;
+ use Symfony\Component\Security\Http\Authenticator\Passport\Badge\PasswordUpgradeBadge;
+ use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
+ use Symfony\Component\Security\Http\Authenticator\Passport\PassportInterface;
+
+ class LoginAuthenticator extends AbstractAuthenticator
+ {
+ public function authenticate(Request $request): PassportInterface
+ {
+ $password = $request->request->get('password');
+ $username = $request->request->get('username');
+ $csrfToken = $request->request->get('csrf_token');
+
+ // ... get the $user from the $username and validate no
+ // parameter is empty
+
+ return new Passport($user, new PasswordCredentials($password), [
+ // $this->userRepository must implement PasswordUpgraderInterface
+ new PasswordUpgradeBadge($password, $this->userRepository),
+ new CsrfTokenBadge('login', $csrfToken);
+ ]);
+ }
+ }
diff --git a/security/guard_authentication.rst b/security/guard_authentication.rst
index 71859870d11..d88bea0de20 100644
--- a/security/guard_authentication.rst
+++ b/security/guard_authentication.rst
@@ -14,6 +14,11 @@ Guard authentication can be used to:
and many more. In this example, we'll build an API token authentication
system, so we can learn more about Guard in detail.
+.. tip::
+
+ A :doc:`new experimental authenticator-based system `
+ was introduced in Symfony 5.1, which will eventually replace Guards in Symfony 6.0.
+
Step 1) Prepare your User Class
-------------------------------