From 600bb80835fc319e1fa87a314d939f30ceb97c3a Mon Sep 17 00:00:00 2001 From: Florent Morselli Date: Thu, 11 Aug 2022 13:44:45 +0200 Subject: [PATCH 1/3] Access Token Documentation --- security.rst | 9 ++ security/access_token.rst | 182 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 191 insertions(+) create mode 100644 security/access_token.rst 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..79eb7b9449d --- /dev/null +++ b/security/access_token.rst @@ -0,0 +1,182 @@ +.. index:: + single: Security; Access Token + +How to use Access Token Authentication +====================================== + +Access tokens are commonly used in API contexts. The access token is obtained +through an authorization server (or similar) whose role is to verify the user identity +and receive consent before the token is issued. + +Access Tokens can be of any kind: opaque strings, Json Web Tokens (JWT) or SAML2 (XML structures). +Please refer to the `RFC6750`_: *The OAuth 2.0 Authorization Framework: Bearer Token Usage*. + +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 retrieves the user identifier from the token. +In order to get the user identifier, implementations may need to load and validate +the token (e.g. revocation, expiration time, digital signature...). + +.. configuration-block:: + + .. code-block:: yaml + + # config/packages/security.yaml + security: + firewalls: + main: + access_token: + token_handler: App\Security\AccessTokenHandler + +This handler shall implement the interface +:class:`Symfony\\Component\\Security\\Http\\AccessToken\\AccessTokenHandlerInterface`. +In the following example, the handler will retrieve the token from a database +using a fictive repository. + +.. configuration-block:: + + .. code-block:: php + + // src/Security/AccessTokenHandler.php + namespace App\Security; + + use App\Repository\AccessTokenRepository; + use Symfony\Component\Security\Http\AccessToken\AccessTokenHandlerInterface; + + class AccessTokenHandler implements AccessTokenHandlerInterface + { + public function __construct( + private readonly AccessTokenRepository $repository + ) { + } + + public function getUserIdentifierFrom(string $token): string + { + $accessToken = $this->repository->findOneByValue($token); + if ($accessToken === null || !$accessToken->isValid()) { + throw new BadCredentialsException('Invalid credentials.'); + } + + return $accessToken->getUserId(); + } + } + +.. caution:: + + It is important to check the token is valid. + For instance, in the example we verify 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``. + +Customizing the Authenticator +----------------------------- + +1) Access Token Extractors + +By default, the access token is read from the request header parameter ``Authorization`` with the scheme ``Bearer``. +You can change the behavior and send the access token through different ways. + +This authenticator provides services able to extract the access token as per the RFC6750: + +- ``header`` or ``security.access_token_extractor.header``: the token is sent through the request header. Usually ``Authorization`` with the ``Bearer`` scheme. +- ``query_string`` or ``security.access_token_extractor.query_string``: the token is part of the query string. Usually ``access_token``. +- ``request_body`` or ``security.access_token_extractor.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. + +Also, you can also create a custom extractor. The class shall implement the interface +: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 + token_extractors: 'my_custom_access_token_extractor' + +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' + - 'request_body' + - 'query_string' + - 'my_custom_access_token_extractor' + +2) Customizing the Success Handler + +Sometimes, the default success handling does not fit your use-case (e.g. +when you need to generate and return additional response header parameters). +To customize how the success handler behaves, create your own handler as a class that implements +:class:`Symfony\\Component\\Security\\Http\\Authentication\\AuthenticationSuccessHandlerInterface`:: + + // src/Security/Authentication/AuthenticationSuccessHandler.php + namespace App\Security\Authentication; + + use Symfony\Component\HttpFoundation\JsonResponse; + use Symfony\Component\HttpFoundation\Request; + use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; + use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface; + + class AuthenticationSuccessHandler implements AuthenticationSuccessHandlerInterface + { + public function onAuthenticationSuccess(Request $request, TokenInterface $token): JsonResponse + { + $user = $token->getUser(); + $userApiToken = $user->getApiToken(); + + return new JsonResponse(['apiToken' => $userApiToken]); + } + } + +Then, configure this 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 + +.. 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`. + +.. _`RFC6750`: https://datatracker.ietf.org/doc/html/rfc6750 From b65c136850d2c50ba8d1b47f3a840a823771b26e Mon Sep 17 00:00:00 2001 From: Wouter de Jong Date: Sat, 5 Nov 2022 15:02:21 +0100 Subject: [PATCH 2/3] Finish the docs for the new Access token authenticator --- security/access_token.rst | 317 +++++++++++++++++++++++++++++--------- 1 file changed, 241 insertions(+), 76 deletions(-) diff --git a/security/access_token.rst b/security/access_token.rst index 79eb7b9449d..65fd072cc79 100644 --- a/security/access_token.rst +++ b/security/access_token.rst @@ -4,12 +4,16 @@ How to use Access Token Authentication ====================================== -Access tokens are commonly used in API contexts. The access token is obtained -through an authorization server (or similar) whose role is to verify the user identity -and receive consent before the token is issued. +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: opaque strings, Json Web Tokens (JWT) or SAML2 (XML structures). -Please refer to the `RFC6750`_: *The OAuth 2.0 Authorization Framework: Bearer Token Usage*. +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 ------------------------------------ @@ -22,9 +26,10 @@ this is not yet the case. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ To use the access token authenticator, you must configure a ``token_handler``. -The token handler retrieves the user identifier from the token. -In order to get the user identifier, implementations may need to load and validate -the token (e.g. revocation, expiration time, digital signature...). +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:: @@ -37,69 +42,108 @@ the token (e.g. revocation, expiration time, digital signature...). access_token: token_handler: App\Security\AccessTokenHandler -This handler shall implement the interface -:class:`Symfony\\Component\\Security\\Http\\AccessToken\\AccessTokenHandlerInterface`. -In the following example, the handler will retrieve the token from a database -using a fictive repository. - -.. configuration-block:: + .. code-block:: xml + + + + + + + + + + + .. code-block:: php - // src/Security/AccessTokenHandler.php - namespace App\Security; + // config/packages/security.php + use App\Security\AccessTokenHandler; + use Symfony\Config\SecurityConfig; - use App\Repository\AccessTokenRepository; - use Symfony\Component\Security\Http\AccessToken\AccessTokenHandlerInterface; + return static function (SecurityConfig $security) { + $security->firewall('main') + ->accessToken() + ->tokenHandler(AccessTokenHandler::class) + ; + }; - class AccessTokenHandler implements AccessTokenHandlerInterface - { - public function __construct( - private readonly AccessTokenRepository $repository - ) { - } +This handler must implement +:class:`Symfony\\Component\\Security\\Http\\AccessToken\\AccessTokenHandlerInterface`:: + + // src/Security/AccessTokenHandler.php + namespace App\Security; - public function getUserIdentifierFrom(string $token): string - { - $accessToken = $this->repository->findOneByValue($token); - if ($accessToken === null || !$accessToken->isValid()) { - throw new BadCredentialsException('Invalid credentials.'); - } + use App\Repository\AccessTokenRepository; + use Symfony\Component\Security\Http\AccessToken\AccessTokenHandlerInterface; - return $accessToken->getUserId(); + class AccessTokenHandler implements AccessTokenHandlerInterface + { + public function __construct( + private AccessTokenRepository $repository + ) { + } + + public function getUserIdentifierFrom(string $token): string + { + // e.g. query the "access token" database to search for this token + $accessToken = $this->repository->findOneByValue($token); + if ($accessToken === null || !$accessToken->isValid()) { + throw new BadCredentialsException('Invalid credentials.'); } + + // and return the user identifier from the found token + return $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 is valid. - For instance, in the example we verify 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``. + 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``. -Customizing the Authenticator ------------------------------ +2) Configure the Token Extractor (Optional) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -1) Access Token Extractors +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``. -You can change the behavior and send the access token through different ways. +By default, the access token is read from the request header parameter +``Authorization`` with the scheme ``Bearer`` (e.g. ``Authorization: Bearer +the-token-value``). -This authenticator provides services able to extract the access token as per the RFC6750: +Symfony provides other extractors as per the `RFC6750`_: -- ``header`` or ``security.access_token_extractor.header``: the token is sent through the request header. Usually ``Authorization`` with the ``Bearer`` scheme. -- ``query_string`` or ``security.access_token_extractor.query_string``: the token is part of the query string. Usually ``access_token``. -- ``request_body`` or ``security.access_token_extractor.request_body``: the token is part of the request body during a POST request. Usually ``access_token``. +``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. + 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. -Also, you can also create a custom extractor. The class shall implement the interface +You can also create a custom extractor. The class must implement :class:`Symfony\\Component\\Security\\Http\\AccessToken\\AccessTokenExtractorInterface`. .. configuration-block:: @@ -112,10 +156,60 @@ Also, you can also create a custom extractor. The class shall implement the inte main: access_token: token_handler: App\Security\AccessTokenHandler - token_extractors: 'my_custom_access_token_extractor' -It is possible to set multiple extractors. -In this case, **the order is important**: the first in the list is called first. + # 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:: @@ -129,37 +223,70 @@ In this case, **the order is important**: the first in the list is called first. token_handler: App\Security\AccessTokenHandler token_extractors: - 'header' - - 'request_body' - - 'query_string' - - 'my_custom_access_token_extractor' + - 'App\Security\CustomTokenExtractor' + + .. code-block:: xml + + + + + + + + + header + App\Security\CustomTokenExtractor + + + + -2) Customizing the Success Handler + .. code-block:: php -Sometimes, the default success handling does not fit your use-case (e.g. -when you need to generate and return additional response header parameters). -To customize how the success handler behaves, create your own handler as a class that implements -:class:`Symfony\\Component\\Security\\Http\\Authentication\\AuthenticationSuccessHandlerInterface`:: + // config/packages/security.php + use App\Security\AccessTokenHandler; + use App\Security\CustomTokenExtractor; + use Symfony\Config\SecurityConfig; - // src/Security/Authentication/AuthenticationSuccessHandler.php - namespace App\Security\Authentication; + return static function (SecurityConfig $security) { + $security->firewall('main') + ->accessToken() + ->tokenHandler(AccessTokenHandler::class) + ->tokenExtractors([ + 'header', + CustomTokenExtractor::class, + ]) + ; + }; - use Symfony\Component\HttpFoundation\JsonResponse; - use Symfony\Component\HttpFoundation\Request; - use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; - use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface; +3) Submit a Request +~~~~~~~~~~~~~~~~~~~ - class AuthenticationSuccessHandler implements AuthenticationSuccessHandlerInterface - { - public function onAuthenticationSuccess(Request $request, TokenInterface $token): JsonResponse - { - $user = $token->getUser(); - $userApiToken = $user->getApiToken(); +That's it! Your application can now authenticate incoming requests using an +API token. - return new JsonResponse(['apiToken' => $userApiToken]); - } - } +Using the default header extractor, you can test the feature by submitting +a request like this: -Then, configure this service ID as the ``success_handler``: +.. 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:: @@ -173,10 +300,48 @@ Then, configure this service ID as the ``success_handler``: 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 From f3f47adb6446934f6b5338825e5cb7a2fe7fae1b Mon Sep 17 00:00:00 2001 From: Florent Morselli Date: Fri, 25 Nov 2022 09:40:47 +0100 Subject: [PATCH 3/3] Update docs for 6.2.0-RC1 changes to TokenHandlerInterface Co-authored-by: Vincent --- security/access_token.rst | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/security/access_token.rst b/security/access_token.rst index 65fd072cc79..e29267585c6 100644 --- a/security/access_token.rst +++ b/security/access_token.rst @@ -82,6 +82,7 @@ This handler must implement use App\Repository\AccessTokenRepository; use Symfony\Component\Security\Http\AccessToken\AccessTokenHandlerInterface; + use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge; class AccessTokenHandler implements AccessTokenHandlerInterface { @@ -90,16 +91,16 @@ This handler must implement ) { } - public function getUserIdentifierFrom(string $token): string + public function getUserBadgeFrom(string $accessToken): UserBadge { // e.g. query the "access token" database to search for this token $accessToken = $this->repository->findOneByValue($token); - if ($accessToken === null || !$accessToken->isValid()) { + if (null === $accessToken || !$accessToken->isValid()) { throw new BadCredentialsException('Invalid credentials.'); } - // and return the user identifier from the found token - return $accessToken->getUserId(); + // and return a UserBadge object containing the user identifier from the found token + return new UserBadge($accessToken->getUserId()); } }