Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Oauth2Session.fetch_token() : Invalid client_id for client_credentials grant with BackendApplicationClient #479

Open
carolinarsm opened this issue Jan 15, 2022 · 0 comments

Comments

@carolinarsm
Copy link

tl;dr: when implementing oauth2 with client_credentials grant type, setting include_client_id=True in fetch_token works for the intended purposes.

I'm implementing a server-to-server client as specified here: https://hl7.org/fhir/uv/bulkdata/authorization/index.html
I don't think many apps use this workflow, but maybe this info could help someone.

For an oauth2 backend confidential client with grant type client_credentials (i.e., RS384 JWT), setting include_client_id=True when calling fetch_token works for the intended purpose, however, this is a bit misleading, since the client id is encoded in the signed JWT that's added to the body for POST request, and commonly client_id is not sent explicitly.

I was a bit confused with fetch_token, because of this:

param include_client_id: Should the request body include the
                                  `client_id` parameter. Default is `None`,
                                  which will attempt to autodetect. This can be
                                  forced to always include (True) or never
                                  include (False).

I intuitively used include_client_id=False because of what I mentioned in the beginning, but mainly because it matches the oauthlib.oauth2.rfc6749.clients definition for BackendApplicationClient, where the default in BackendApplicationClient.prepare_request_body is include_client_id=False.

What is happening when using include_client_id=None or include_client_id=False (pseudocode)

from requests_oauthlib import OAuth2Session
from oauthlib.oauth2 import BackendApplicationClient
from authlib.jose import jwt  # this is from authlib

# Client and Session
client = BackendApplicationClient(client_id=preregistered_backend_client_id)
session = OAuth2Session(client=client)

# Generate a signature with our preregistered credentials, and create a signed JWT
signed_jwt = jwt.encode(header=jwt_header, payload=jwt_payload, key=my_private_key).decode()

token_request_body = {
            'client_assertion_type': 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer', 
            'client_assertion': signed_jwt
        } 

encoded_jwt_body = client.prepare_request_body(body=urlencode(token_request_body))

# Fetching access_token:
token_dict = session.fetch_token(token_url, body=encoded_jwt_body)
>>>  oauthlib.oauth2.rfc6749.errors.InvalidClientIdError: (invalid_request) Invalid client_id parameter value

Since in our call we are not passing an auth and include_client_id is not True, the logic in requests_oauthlib.OAuth2Session.fetch_token results in

  • creating a HTTPBasic auth that we can't use with our grant type
  • this auth being passed to the POST request, resulting in an error
if auth is not None: 
            if include_client_id is None:
                include_client_id = False

else: 
	if include_client_id is not True:
		if client_id:
                    log.debug(
                        'Encoding `client_id` "%s" with `client_secret` '
                        "as Basic auth credentials.",
                        client_id,
                    )
                    client_secret = client_secret if client_secret is not None else ""
                    auth = requests.auth.HTTPBasicAuth(client_id, client_secret)

The encoded body we passed to fetch_token is added to request_kwargs, wich is correct and should be enough, however, the faulty auth is also in the request:

if method.upper() == "POST":
            request_kwargs["params" if force_querystring else "data"] = dict(
                urldecode(body)
            )

r = self.request(
            method=method,
            url=token_url,
            timeout=timeout,
            headers=headers,
            **auth=auth**, # ---> Nope
            verify=verify,
            proxies=proxies,
            **request_kwargs
        )

This request should work correctly with the token_url and **kwargs containing the encoded body, but maybe I'm no seeing something?
Any feedback welcome.

-Cheers

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant