.. _specs/rfc7662:

RFC7662: OAuth 2.0 Token Introspection
======================================

.. meta::
    :description: API references on RFC7009, OAuth 2.0 Token Introspection,
        in Python via Authlib implementation.

This section contains the generic implementation of RFC7662_. OAuth 2.0 Token
Introspection is usually designed to let resource servers to know content of
a token.

.. _RFC7662: https://tools.ietf.org/html/rfc7662

.. module:: authlib.oauth2.rfc7662

.. _register_introspection_endpoint:

Register Introspection Endpoint
-------------------------------

.. versionchanged:: v1.0

Authlib is designed to be very extendable, with the method of
``.register_endpoint`` on ``AuthorizationServer``, it is easy to add the
introspection endpoint to the authorization server. It works on both
:ref:`flask_oauth2_server` and :ref:`django_oauth2_server`. But first,
we need to implement the missing methods::

    from authlib.oauth2.rfc7662 import IntrospectionEndpoint

    class MyIntrospectionEndpoint(IntrospectionEndpoint):
        def query_token(self, token, token_type_hint):
            if token_type_hint == 'access_token':
                tok = Token.query.filter_by(access_token=token).first()
            elif token_type_hint == 'refresh_token':
                tok = Token.query.filter_by(refresh_token=token).first()
            else:
                # without token_type_hint
                tok = Token.query.filter_by(access_token=token).first()
                if not tok:
                    tok = Token.query.filter_by(refresh_token=token).first()
            return tok

        def introspect_token(self, token):
            return {
                'active': True,
                'client_id': token.client_id,
                'token_type': token.token_type,
                'username': get_token_username(token),
                'scope': token.get_scope(),
                'sub': get_token_user_sub(token),
                'aud': token.client_id,
                'iss': 'https://server.example.com/',
                'exp': token.expires_at,
                'iat': token.issued_at,
            }

        def check_permission(self, token, client, request):
            # for example, we only allow internal client to access introspection endpoint
            return client.client_type == 'internal'

    # register it to authorization server
    server.register_endpoint(MyIntrospectionEndpoint)

After the registration, we can create a response with::

    @app.route('/oauth/introspect', methods=['POST'])
    def introspect_token():
        return server.create_endpoint_response(MyIntrospectionEndpoint.ENDPOINT_NAME)


.. _require_oauth_introspection:

Use Introspection in Resource Server
------------------------------------

.. versionadded:: v1.0

When resource server has no access to token database, it can use introspection
endpoint to validate the given token. Here is how::

    import requests
    from authlib.oauth2.rfc7662 import IntrospectTokenValidator
    from your_project import secrets

    class MyIntrospectTokenValidator(IntrospectTokenValidator):
        def introspect_token(self, token_string):
            url = 'https://example.com/oauth/introspect'
            data = {'token': token_string, 'token_type_hint': 'access_token'}
            auth = (secrets.internal_client_id, secrets.internal_client_secret)
            resp = requests.post(url, data=data, auth=auth)
            resp.raise_for_status()
            return resp.json()

We can then register this token validator in to resource protector::

    require_oauth = ResourceProtector()
    require_oauth.register_token_validator(MyIntrospectTokenValidator())

Please note, when using ``IntrospectTokenValidator``, the ``current_token`` will be
a dict.

API Reference
-------------

.. autoclass:: IntrospectionEndpoint
    :member-order: bysource
    :members:
    :inherited-members:

.. autoclass:: IntrospectTokenValidator
    :members:
