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.
tl;dr: when implementing oauth2 with
client_credentials
grant type, settinginclude_client_id=True
infetch_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), settinginclude_client_id=True
when callingfetch_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 forPOST
request, and commonlyclient_id
is not sent explicitly.I was a bit confused with
fetch_token
, because of this:I intuitively used
include_client_id=False
because of what I mentioned in the beginning, but mainly because it matches theoauthlib.oauth2.rfc6749.clients
definition for BackendApplicationClient, where the default inBackendApplicationClient.prepare_request_body
isinclude_client_id=False
.What is happening when using
include_client_id=None
orinclude_client_id=False
(pseudocode)Since in our call we are not passing an
auth
andinclude_client_id
is not True, the logic inrequests_oauthlib.OAuth2Session.fetch_token
results inPOST
request, resulting in an errorThe encoded body we passed to
fetch_token
is added torequest_kwargs
, wich is correct and should be enough, however, the faultyauth
is also in the request: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