requests-oauthlib icon indicating copy to clipboard operation
requests-oauthlib copied to clipboard

BUG - Unable to set multiple 'resource' parameters in token request body, as specified in RFC 8707 : Resource Indicators for OAuth 2.0

Open nuxwin opened this issue 11 months ago • 1 comments

Hi,

I'm using latest version of the requests-authlibpackage.

For our authorization server, we added support for audience claim, according RFC 8707. So, basically put, one client can request access to one or many resources by adding the resource parameter in the token request body. When requesting access to many resources, many 'resource' parameters need to be added into the token request rather than a single 'resource' parameter with space-separated resource values (resource URI), as it is done with scope claim.

For instance, for a token request using the client credentials grant (POST request):

POST /token HTTP/1.1
Host: server.example.com
Authorization: Basic xxxxxxxxxxxxxxxxxxxxxxxx
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials&resource=https//resourceserver1.example.com&resource=https//resourceserver2.example.com&scope=read+write

The problem is that with your current implementation, resulting Request kwargs are wrong.

  1. Result when passing the multiple resource parameters through kwargs, as a list:
Request kwargs: {'data': {'grant_type': 'client_credentials', 'scope': 'read write', 'resource': "['https://resourceserver1.example.com', 'https://resourceserver2.example.com']"}}

As you can see the list of resourceparameters is encoded as single string which is wrong.

  1. Result when passing the multiple resourceparameters through body:
Request kwargs: {'data': {'resource': 'https://resourceserver2.example.com', 'grant_type': 'client_credentials', 'scope': 'read write'}}

As you can see, only one resourceparameter is kept which is wrong too.

For the record, I tried with the following code:

By passing multiple resource parameters in body:

token = OAuth2Session(client=BackendApplicationClient(client_id=client_id)).fetch_token(
    token_url='https://my-oauthorization-server.tld/o/token',
    client_id=client_id,
    client_secret=client_secret,
    body="resource=https://resourceserver1.example.com&resource=https://resourceserver2.example.com",
    scope=["read", "write"]
)

By passing multiple resource parameters as kwargs:

token = OAuth2Session(client=BackendApplicationClient(client_id=client_id)).fetch_token(
    token_url='https://my-oauthorization-server.tld/o/token',
    client_id=client_id,
    client_secret=client_secret,
    resource=["https://resourceserver1.example.com", "https://resourceserver2.example.com"],
    scope=["read", "write"]
)

Thank you.

nuxwin avatar Dec 28 '24 18:12 nuxwin

Something that seem to work but... really ugly:

import ast

from oauthlib.oauth2 import BackendApplicationClient
from requests_oauthlib import OAuth2Session


# Register a hook to add multiple resource parameters to the token request as per RFC 8707.
def _oauth2_resource_indicators_injector(token_url, headers, request_kwargs):
    # Modify the resource parameter to be a list
    if "data" in request_kwargs and isinstance(request_kwargs["data"], dict) and "resource" in request_kwargs["data"]:
        # Convert the string that looks like a list into an actual list using ast.literal_eval
        try:
            resource_list = ast.literal_eval(request_kwargs["data"]["resource"])
            # Ensure it's a list.
            if isinstance(resource_list, list):
                # Overwrite the resource parameter with the list.
                request_kwargs["data"]["resource"] = resource_list
        except (ValueError, SyntaxError):
            # If conversion fails, leave it as is.
            pass

    return token_url, headers, request_kwargs


oauth_session = OAuth2Session(client=BackendApplicationClient(client_id='abcdefg'))
oauth_session.register_compliance_hook("access_token_request", _oauth2_resource_indicators_injector)
token = oauth_session.fetch_token(
    token_url="https://my-oauthorization-server.tld/o/token",
    client_id="abcdefg",
    client_secret="abcdefgabcdefgabcdefgabcdefg",
    resource=[
        "https://api1.example.com",
        "https://api2.example.com"
    ],
    scope=["read", "write"]
)

Result (prepared request body):

grant_type=client_credentials&scope=read+write&resource=https%3A%2F%2Fapi1.example.com&resource=https%3A%2F%2Fapi2.example.com

but hooks are intented to be used for non compliant providers. From my point of view, here, the non compliant part is OAuth2Session.

nuxwin avatar Dec 29 '24 01:12 nuxwin