@@ -1325,6 +1325,51 @@ async def test_basic_auth_client_id_mismatch_fails(
13251325 # RFC 6749: authentication failures return "invalid_client"
13261326 assert error_response ["error" ] == "invalid_client"
13271327 assert "Client ID mismatch" in error_response ["error_description" ]
1328+
1329+ @pytest .mark .anyio
1330+ async def test_basic_auth_without_client_id_at_body (
1331+ self , test_client : httpx .AsyncClient , mock_oauth_provider : MockOAuthProvider , pkce_challenge : dict [str , str ]
1332+ ):
1333+ """Test that Basic auth works even if client_id is missing from body."""
1334+ client_metadata = {
1335+ "redirect_uris" : ["https://client.example.com/callback" ],
1336+ "client_name" : "Basic Auth Client" ,
1337+ "token_endpoint_auth_method" : "client_secret_basic" ,
1338+ "grant_types" : ["authorization_code" , "refresh_token" ],
1339+ }
1340+
1341+ response = await test_client .post ("/register" , json = client_metadata )
1342+ assert response .status_code == 201
1343+ client_info = response .json ()
1344+
1345+ auth_code = f"code_{ int (time .time ())} "
1346+ mock_oauth_provider .auth_codes [auth_code ] = AuthorizationCode (
1347+ code = auth_code ,
1348+ client_id = client_info ["client_id" ],
1349+ code_challenge = pkce_challenge ["code_challenge" ],
1350+ redirect_uri = AnyUrl ("https://client.example.com/callback" ),
1351+ redirect_uri_provided_explicitly = True ,
1352+ scopes = ["read" , "write" ],
1353+ expires_at = time .time () + 600 ,
1354+ )
1355+
1356+ credentials = f"{ client_info ['client_id' ]} :{ client_info ['client_secret' ]} "
1357+ encoded_credentials = base64 .b64encode (credentials .encode ()).decode ()
1358+
1359+ response = await test_client .post (
1360+ "/token" ,
1361+ headers = {"Authorization" : f"Basic { encoded_credentials } " },
1362+ data = {
1363+ "grant_type" : "authorization_code" ,
1364+ # client_id omitted from body
1365+ "code" : auth_code ,
1366+ "code_verifier" : pkce_challenge ["code_verifier" ],
1367+ "redirect_uri" : "https://client.example.com/callback" ,
1368+ },
1369+ )
1370+ assert response .status_code == 200
1371+ token_response = response .json ()
1372+ assert "access_token" in token_response
13281373
13291374 @pytest .mark .anyio
13301375 async def test_none_auth_method_public_client (
0 commit comments