@@ -1194,17 +1194,31 @@ async def _exchange_code_for_tokens(self, credentials: Dict[str, Any], code: str
11941194 token_url = runtime_credentials ["token_url" ]
11951195 redirect_uri = runtime_credentials ["redirect_uri" ]
11961196
1197+ # Determine token endpoint authentication method (RFC 6749 Section 2.3)
1198+ # - "client_secret_post" (default): client_id and client_secret in POST body
1199+ # - "client_secret_basic": credentials in Authorization: Basic header
1200+ token_auth_method = credentials .get ("token_endpoint_auth_method" , "client_secret_post" )
1201+ use_basic_auth = token_auth_method == "client_secret_basic" and client_secret
1202+
1203+ # Build HTTP Basic Auth header if required by the provider
1204+ auth_header = None
1205+ if use_basic_auth :
1206+ basic_credentials = base64 .b64encode (f"{ client_id } :{ client_secret } " .encode ()).decode ()
1207+ auth_header = {"Authorization" : f"Basic { basic_credentials } " }
1208+
11971209 # Prepare token exchange data
11981210 token_data = {
11991211 "grant_type" : "authorization_code" ,
12001212 "code" : code ,
12011213 "redirect_uri" : redirect_uri ,
1202- "client_id" : client_id ,
12031214 }
12041215
1205- # Only include client_secret if present (public clients don't have secrets)
1206- if client_secret :
1207- token_data ["client_secret" ] = client_secret
1216+ # Include client credentials in POST body only when not using Basic auth
1217+ if not use_basic_auth :
1218+ token_data ["client_id" ] = client_id
1219+ # Only include client_secret if present (public clients don't have secrets)
1220+ if client_secret :
1221+ token_data ["client_secret" ] = client_secret
12081222
12091223 # Add PKCE code_verifier if present (RFC 7636)
12101224 if code_verifier :
@@ -1229,7 +1243,7 @@ async def _exchange_code_for_tokens(self, credentials: Dict[str, Any], code: str
12291243 for attempt in range (self .max_retries ):
12301244 try :
12311245 client = await self ._get_client ()
1232- response = await client .post (token_url , data = token_data , timeout = self .request_timeout )
1246+ response = await client .post (token_url , data = token_data , headers = auth_header , timeout = self .request_timeout )
12331247 response .raise_for_status ()
12341248
12351249 # GitHub returns form-encoded responses, not JSON
@@ -1294,16 +1308,28 @@ async def refresh_token(self, refresh_token: str, credentials: Dict[str, Any]) -
12941308 if not client_id :
12951309 raise OAuthError ("No client_id configured for OAuth provider" )
12961310
1311+ # Determine token endpoint authentication method (RFC 6749 Section 2.3)
1312+ token_auth_method = credentials .get ("token_endpoint_auth_method" , "client_secret_post" )
1313+ use_basic_auth = token_auth_method == "client_secret_basic" and client_secret
1314+
1315+ # Build HTTP Basic Auth header if required by the provider
1316+ auth_header = None
1317+ if use_basic_auth :
1318+ basic_credentials = base64 .b64encode (f"{ client_id } :{ client_secret } " .encode ()).decode ()
1319+ auth_header = {"Authorization" : f"Basic { basic_credentials } " }
1320+
12971321 # Prepare token refresh request
12981322 token_data = {
12991323 "grant_type" : "refresh_token" ,
13001324 "refresh_token" : refresh_token ,
1301- "client_id" : client_id ,
13021325 }
13031326
1304- # Add client_secret if available (some providers require it)
1305- if client_secret :
1306- token_data ["client_secret" ] = client_secret
1327+ # Include client credentials in POST body only when not using Basic auth
1328+ if not use_basic_auth :
1329+ token_data ["client_id" ] = client_id
1330+ # Add client_secret if available (some providers require it)
1331+ if client_secret :
1332+ token_data ["client_secret" ] = client_secret
13071333
13081334 # Add resource parameter for JWT access token (RFC 8707)
13091335 # Must be included in refresh requests to maintain JWT token type
@@ -1324,7 +1350,7 @@ async def refresh_token(self, refresh_token: str, credentials: Dict[str, Any]) -
13241350 for attempt in range (self .max_retries ):
13251351 try :
13261352 client = await self ._get_client ()
1327- response = await client .post (token_url , data = token_data , timeout = self .request_timeout )
1353+ response = await client .post (token_url , data = token_data , headers = auth_header , timeout = self .request_timeout )
13281354 if response .status_code == 200 :
13291355 token_response = response .json ()
13301356
0 commit comments