Skip to content

Commit 1ac3a70

Browse files
committed
[Security][SecurityBundle] Document the oauth2 token handler options
1 parent 7063fcc commit 1ac3a70

1 file changed

Lines changed: 235 additions & 0 deletions

File tree

security/access_token.rst

Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -833,6 +833,240 @@ useful when developing or testing applications that use OIDC authentication:
833833

834834
The JWK used for signing must have the appropriate `key operation flags`_ set.
835835

836+
Using OAuth 2.0 Token Introspection
837+
-----------------------------------
838+
839+
`RFC 7662`_ defines an introspection endpoint, where a resource server
840+
asks the authorization server what it knows about an access token. The
841+
``oauth2`` token handler posts the token to that endpoint and builds the
842+
user out of the answer, so your application never has to read the token
843+
itself.
844+
845+
This token handler requires the ``symfony/http-client`` package to make
846+
the needed HTTP requests. If you haven't installed it yet, run this
847+
command:
848+
849+
.. code-block:: terminal
850+
851+
$ composer require symfony/http-client
852+
853+
Where the authorization server lives and how your application
854+
authenticates there belongs to the HTTP client, not to the firewall.
855+
Declare a scoped client whose ``base_uri`` is the introspection endpoint
856+
and whose ``auth_basic`` holds the credentials of your resource server:
857+
858+
.. configuration-block::
859+
860+
.. code-block:: yaml
861+
862+
# config/packages/framework.yaml
863+
framework:
864+
http_client:
865+
scoped_clients:
866+
oauth2.introspection:
867+
base_uri: 'https://auth.example.com/introspect'
868+
auth_basic: '%env(OAUTH2_ID)%:%env(OAUTH2_SECRET)%'
869+
870+
.. code-block:: php
871+
872+
// config/packages/framework.php
873+
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
874+
875+
return App::config([
876+
'framework' => [
877+
'http_client' => [
878+
'scoped_clients' => [
879+
'oauth2.introspection' => [
880+
'base_uri' => 'https://auth.example.com/introspect',
881+
'auth_basic' => '%env(OAUTH2_ID)%:%env(OAUTH2_SECRET)%',
882+
],
883+
],
884+
],
885+
],
886+
]);
887+
888+
Then give the service ID of that client to the ``http_client`` option of
889+
the token handler, together with what the response is confronted with:
890+
891+
.. configuration-block::
892+
893+
.. code-block:: yaml
894+
895+
# config/packages/security.yaml
896+
security:
897+
firewalls:
898+
main:
899+
access_token:
900+
token_handler:
901+
oauth2:
902+
http_client: 'oauth2.introspection'
903+
issuer: 'https://auth.example.com/'
904+
audience: 'https://api.example.com'
905+
claim: 'sub'
906+
allowed_time_drift: 5
907+
908+
.. code-block:: php
909+
910+
// config/packages/security.php
911+
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
912+
913+
return App::config([
914+
'security' => [
915+
'firewalls' => [
916+
'main' => [
917+
'access_token' => [
918+
'token_handler' => [
919+
'oauth2' => [
920+
'http_client' => 'oauth2.introspection',
921+
'issuer' => 'https://auth.example.com/',
922+
'audience' => 'https://api.example.com',
923+
'claim' => 'sub',
924+
'allowed_time_drift' => 5,
925+
],
926+
],
927+
],
928+
],
929+
],
930+
],
931+
]);
932+
933+
These are the available options:
934+
935+
``http_client``
936+
Service ID of the HTTP client the introspection endpoint is called
937+
with. It defaults to the ``http_client`` service, which carries
938+
neither that endpoint nor any credentials, so declare a scoped client
939+
as shown above.
940+
941+
``issuer``
942+
Identifier of the authorization server, checked against the ``iss``
943+
member of the introspection response. It defaults to ``null``, which
944+
skips that check.
945+
946+
``audience``
947+
Identifiers of your resource server, one of which the ``aud`` member
948+
of the response must name. Give a single identifier as a string and
949+
several ones as a list; one match is enough, because an access token
950+
minted for several resource servers is meant for each of them. It
951+
defaults to an empty list, which skips that check.
952+
953+
``claim``
954+
Claim holding the user identifier (e.g. ``sub``, ``username``,
955+
``email``). It defaults to ``null``, which reads the ``sub`` claim and
956+
falls back to the ``username`` one.
957+
958+
``allowed_time_drift``
959+
Tolerance, in seconds, on the ``iat``, ``nbf`` and ``exp`` members of
960+
the response, to account for the clocks of the two servers running
961+
slightly apart. It defaults to ``0``.
962+
963+
When the HTTP client is the only thing you configure, give it as a
964+
string:
965+
966+
.. configuration-block::
967+
968+
.. code-block:: yaml
969+
970+
# config/packages/security.yaml
971+
security:
972+
firewalls:
973+
main:
974+
access_token:
975+
token_handler:
976+
oauth2: 'oauth2.introspection'
977+
978+
.. code-block:: php
979+
980+
// config/packages/security.php
981+
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
982+
983+
return App::config([
984+
'security' => [
985+
'firewalls' => [
986+
'main' => [
987+
'access_token' => [
988+
'token_handler' => [
989+
'oauth2' => 'oauth2.introspection',
990+
],
991+
],
992+
],
993+
],
994+
],
995+
]);
996+
997+
A token the authorization server reports as inactive is refused, and so
998+
is a response whose dates place the token outside its validity window,
999+
or whose ``iss`` or ``aud`` members name an issuer or an audience you
1000+
did not declare. `RFC 7662`_ makes all of those members optional, so the
1001+
dates are verified when the response carries them, and the issuer and
1002+
the audience when the firewall declares which ones it accepts.
1003+
1004+
.. versionadded:: 8.2
1005+
1006+
The ``http_client``, ``issuer``, ``audience``, ``claim``,
1007+
``allowed_time_drift`` and ``cache`` options of the ``oauth2`` token
1008+
handler were introduced in Symfony 8.2.
1009+
1010+
Caching the Introspection Responses
1011+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1012+
1013+
Introspecting the token on every request costs a round trip to the
1014+
authorization server. The ``cache`` option stores the responses of
1015+
active tokens in the pool of your choice, which requires the
1016+
``symfony/cache`` package:
1017+
1018+
.. code-block:: terminal
1019+
1020+
$ composer require symfony/cache
1021+
1022+
.. configuration-block::
1023+
1024+
.. code-block:: yaml
1025+
1026+
# config/packages/security.yaml
1027+
security:
1028+
firewalls:
1029+
main:
1030+
access_token:
1031+
token_handler:
1032+
oauth2:
1033+
http_client: 'oauth2.introspection'
1034+
cache:
1035+
id: cache.app
1036+
ttl: 60
1037+
1038+
.. code-block:: php
1039+
1040+
// config/packages/security.php
1041+
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
1042+
1043+
return App::config([
1044+
'security' => [
1045+
'firewalls' => [
1046+
'main' => [
1047+
'access_token' => [
1048+
'token_handler' => [
1049+
'oauth2' => [
1050+
'http_client' => 'oauth2.introspection',
1051+
'cache' => [
1052+
'id' => 'cache.app',
1053+
'ttl' => 60,
1054+
],
1055+
],
1056+
],
1057+
],
1058+
],
1059+
],
1060+
],
1061+
]);
1062+
1063+
The ``id`` option is required and the ``ttl`` one defaults to ``60``
1064+
seconds. The shorter that lifetime is, the sooner a revoked token stops
1065+
being accepted. No entry outlives the ``exp`` the authorization server
1066+
reported, and the response of an inactive token is never stored. Entries
1067+
are keyed by a digest of the token, so the pool holds no usable
1068+
credential.
1069+
8361070
Using CAS 2.0
8371071
-------------
8381072

@@ -1003,6 +1237,7 @@ for :ref:`stateless firewalls <reference-security-stateless>`.
10031237
.. _`OpenID Connect Specification`: https://openid.net/specs/openid-connect-core-1_0.html
10041238
.. _`OpenID Connect Discovery`: https://openid.net/specs/openid-connect-discovery-1_0.html
10051239
.. _`RFC 7517`: https://datatracker.ietf.org/doc/html/rfc7517
1240+
.. _`RFC 7662`: https://datatracker.ietf.org/doc/html/rfc7662
10061241
.. _`RFC6750`: https://datatracker.ietf.org/doc/html/rfc6750
10071242
.. _`SAML2 (XML structures)`: https://docs.oasis-open.org/security/saml/Post2.0/sstc-saml-tech-overview-2.0.html
10081243
.. _`key operation flags`: https://www.iana.org/assignments/jose/jose.xhtml#web-key-operations

0 commit comments

Comments
 (0)