diff --git a/security.rst b/security.rst
index 81a0aa4d0a2..5fe2a26d40d 100644
--- a/security.rst
+++ b/security.rst
@@ -1202,6 +1202,15 @@ website.
You can learn all about this authenticator in :doc:`/security/login_link`.
+Access Tokens
+~~~~~~~~~~~~~
+
+Access Tokens are often used in API contexts.
+The user receives a token from an authorization server
+which authenticates them.
+
+You can learn all about this authenticator in :doc:`/security/access_token`.
+
X.509 Client Certificates
~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/security/access_token.rst b/security/access_token.rst
new file mode 100644
index 00000000000..e29267585c6
--- /dev/null
+++ b/security/access_token.rst
@@ -0,0 +1,348 @@
+.. index::
+ single: Security; Access Token
+
+How to use Access Token Authentication
+======================================
+
+Access tokens or API tokens are commonly used as authentication mechanism
+in API contexts. The access token is a string, obtained during authentication
+(using the application or an authorization server). The access token's role
+is to verify the user identity and receive consent before the token is
+issued.
+
+Access tokens can be of any kind, for instance opaque strings,
+`JSON Web Tokens (JWT)`_ or `SAML2 (XML structures)`_. Please refer to the
+`RFC6750`_: *The OAuth 2.0 Authorization Framework: Bearer Token Usage* for
+a detailed specification.
+
+Using the Access Token Authenticator
+------------------------------------
+
+This guide assumes you have setup security and have created a user object
+in your application. Follow :doc:`the main security guide ` if
+this is not yet the case.
+
+1) Configure the Access Token Authenticator
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+To use the access token authenticator, you must configure a ``token_handler``.
+The token handler receives the token from the request and returns the
+correct user identifier. To get the user identifier, implementations may
+need to load and validate the token (e.g. revocation, expiration time,
+digital signature, etc.).
+
+.. configuration-block::
+
+ .. code-block:: yaml
+
+ # config/packages/security.yaml
+ security:
+ firewalls:
+ main:
+ access_token:
+ token_handler: App\Security\AccessTokenHandler
+
+ .. code-block:: xml
+
+
+
+
+
+
+
+
+
+
+
+
+ .. code-block:: php
+
+ // config/packages/security.php
+ use App\Security\AccessTokenHandler;
+ use Symfony\Config\SecurityConfig;
+
+ return static function (SecurityConfig $security) {
+ $security->firewall('main')
+ ->accessToken()
+ ->tokenHandler(AccessTokenHandler::class)
+ ;
+ };
+
+This handler must implement
+:class:`Symfony\\Component\\Security\\Http\\AccessToken\\AccessTokenHandlerInterface`::
+
+ // src/Security/AccessTokenHandler.php
+ namespace App\Security;
+
+ use App\Repository\AccessTokenRepository;
+ use Symfony\Component\Security\Http\AccessToken\AccessTokenHandlerInterface;
+ use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
+
+ class AccessTokenHandler implements AccessTokenHandlerInterface
+ {
+ public function __construct(
+ private AccessTokenRepository $repository
+ ) {
+ }
+
+ public function getUserBadgeFrom(string $accessToken): UserBadge
+ {
+ // e.g. query the "access token" database to search for this token
+ $accessToken = $this->repository->findOneByValue($token);
+ if (null === $accessToken || !$accessToken->isValid()) {
+ throw new BadCredentialsException('Invalid credentials.');
+ }
+
+ // and return a UserBadge object containing the user identifier from the found token
+ return new UserBadge($accessToken->getUserId());
+ }
+ }
+
+The access token authenticator will use the returned user identifier to
+load the user using the :ref:`user provider `.
+
+.. caution::
+
+ It is important to check the token if is valid. For instance, the
+ example above verifies whether the token has not expired. With
+ self-contained access tokens such as JWT, the handler is required to
+ verify the digital signature and understand all claims, especially
+ ``sub``, ``iat``, ``nbf`` and ``exp``.
+
+2) Configure the Token Extractor (Optional)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The application is now ready to handle incoming tokens. A *token extractor*
+retrieves the token from the request (e.g. a header or request body).
+
+By default, the access token is read from the request header parameter
+``Authorization`` with the scheme ``Bearer`` (e.g. ``Authorization: Bearer
+the-token-value``).
+
+Symfony provides other extractors as per the `RFC6750`_:
+
+``header`` (default)
+ The token is sent through the request header. Usually ``Authorization``
+ with the ``Bearer`` scheme.
+``query_string``
+ The token is part of the request query string. Usually ``access_token``.
+``request_body``
+ The token is part of the request body during a POST request. Usually
+ ``access_token``.
+
+.. caution::
+
+ Because of the security weaknesses associated with the URI method,
+ including the high likelihood that the URL or the request body
+ containing the access token will be logged, methods ``query_string``
+ and ``request_body`` **SHOULD NOT** be used unless it is impossible to
+ transport the access token in the request header field.
+
+You can also create a custom extractor. The class must implement
+:class:`Symfony\\Component\\Security\\Http\\AccessToken\\AccessTokenExtractorInterface`.
+
+.. configuration-block::
+
+ .. code-block:: yaml
+
+ # config/packages/security.yaml
+ security:
+ firewalls:
+ main:
+ access_token:
+ token_handler: App\Security\AccessTokenHandler
+
+ # use a different built-in extractor
+ token_extractors: request_body
+
+ # or provide the service ID of a custom extractor
+ token_extractors: 'App\Security\CustomTokenExtractor'
+
+ .. code-block:: xml
+
+
+
+
+
+
+
+
+
+ request_body
+
+
+ App\Security\CustomTokenExtractor
+
+
+
+
+
+ .. code-block:: php
+
+ // config/packages/security.php
+ use App\Security\AccessTokenHandler;
+ use App\Security\CustomTokenExtractor;
+ use Symfony\Config\SecurityConfig;
+
+ return static function (SecurityConfig $security) {
+ $security->firewall('main')
+ ->accessToken()
+ ->tokenHandler(AccessTokenHandler::class)
+
+ // use a different built-in extractor
+ ->tokenExtractors('request_body')
+
+ # or provide the service ID of a custom extractor
+ ->tokenExtractors(CustomTokenExtractor::class)
+ ;
+ };
+
+It is possible to set multiple extractors. In this case, **the order is
+important**: the first in the list is called first.
+
+.. configuration-block::
+
+ .. code-block:: yaml
+
+ # config/packages/security.yaml
+ security:
+ firewalls:
+ main:
+ access_token:
+ token_handler: App\Security\AccessTokenHandler
+ token_extractors:
+ - 'header'
+ - 'App\Security\CustomTokenExtractor'
+
+ .. code-block:: xml
+
+
+
+
+
+
+
+
+ header
+ App\Security\CustomTokenExtractor
+
+
+
+
+
+ .. code-block:: php
+
+ // config/packages/security.php
+ use App\Security\AccessTokenHandler;
+ use App\Security\CustomTokenExtractor;
+ use Symfony\Config\SecurityConfig;
+
+ return static function (SecurityConfig $security) {
+ $security->firewall('main')
+ ->accessToken()
+ ->tokenHandler(AccessTokenHandler::class)
+ ->tokenExtractors([
+ 'header',
+ CustomTokenExtractor::class,
+ ])
+ ;
+ };
+
+3) Submit a Request
+~~~~~~~~~~~~~~~~~~~
+
+That's it! Your application can now authenticate incoming requests using an
+API token.
+
+Using the default header extractor, you can test the feature by submitting
+a request like this:
+
+.. code-block:: terminal
+
+ $ curl -H 'Authorization: Bearer an-accepted-token-value' \
+ https://localhost:8000/api/some-route
+
+Customizing the Success Handler
+-------------------------------
+
+By default, the request continues (e.g. the controller for the route is
+run). If you want to customize success handling, create your own success
+handler by creating a class that implements
+:class:`Symfony\\Component\\Security\\Http\\Authentication\\AuthenticationSuccessHandlerInterface`
+and configure the service ID as the ``success_handler``:
+
+.. configuration-block::
+
+ .. code-block:: yaml
+
+ # config/packages/security.yaml
+ security:
+ firewalls:
+ main:
+ access_token:
+ token_handler: App\Security\AccessTokenHandler
+ success_handler: App\Security\Authentication\AuthenticationSuccessHandler
+
+ .. code-block:: xml
+
+
+
+
+
+
+
+
+
+
+
+
+ .. code-block:: php
+
+ // config/packages/security.php
+ use App\Security\AccessTokenHandler;
+ use App\Security\Authentication\AuthenticationSuccessHandler;
+ use Symfony\Config\SecurityConfig;
+
+ return static function (SecurityConfig $security) {
+ $security->firewall('main')
+ ->accessToken()
+ ->tokenHandler(AccessTokenHandler::class)
+ ->successHandler(AuthenticationSuccessHandler::class)
+ ;
+ };
+
+.. tip::
+
+ If you want to customize the default failure handling, use the
+ ``failure_handler`` option and create a class that implements
+ :class:`Symfony\\Component\\Security\\Http\\Authentication\\AuthenticationFailureHandlerInterface`.
+
+.. _`Json Web Tokens (JWT)`: https://datatracker.ietf.org/doc/html/rfc7519
+.. _`SAML2 (XML structures)`: https://docs.oasis-open.org/security/saml/Post2.0/sstc-saml-tech-overview-2.0.html
+.. _`RFC6750`: https://datatracker.ietf.org/doc/html/rfc6750