Skip to content

Commit 249195a

Browse files
jochenottjochen-ott-bysfc-gh-pczajka
authored
Add option to send oauth client credentials in body (#2509)
Co-authored-by: Jochen Ott <[email protected]> Co-authored-by: Patryk Czajka <[email protected]>
1 parent 29b5b7e commit 249195a

File tree

4 files changed

+52
-6
lines changed

4 files changed

+52
-6
lines changed

DESCRIPTION.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ Source code is also available at: https://github.com/snowflakedb/snowflake-conne
1010
- v3.18.0(TBD)
1111
- Added the `workload_identity_impersonation_path` parameter to support service account impersonation for Workload Identity Federation on GCP and AWS workloads only
1212
- Fixed `get_results_from_sfqid` when using `DictCursor` and executing multiple statements at once
13+
- Added the `oauth_credentials_in_body` parameter supporting an option to send the oauth client credentials in the request body
1314

1415
- v3.17.3(September 02,2025)
1516
- Enhanced configuration file permission warning messages.

src/snowflake/connector/auth/oauth_credentials.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ def __init__(
2727
token_request_url: str,
2828
scope: str,
2929
connection: SnowflakeConnection | None = None,
30+
credentials_in_body: bool = False,
3031
**kwargs,
3132
) -> None:
3233
self._validate_client_credentials_present(client_id, client_secret, connection)
@@ -40,6 +41,7 @@ def __init__(
4041
**kwargs,
4142
)
4243
self._application = application
44+
self._credentials_in_body = credentials_in_body
4345
self._origin: str | None = None
4446

4547
def _get_oauth_type_id(self) -> str:
@@ -60,4 +62,7 @@ def _request_tokens(
6062
"grant_type": "client_credentials",
6163
"scope": self._scope,
6264
}
65+
if self._credentials_in_body:
66+
fields["client_id"] = self._client_id
67+
fields["client_secret"] = self._client_secret
6368
return self._get_request_token_response(conn, fields)

src/snowflake/connector/connection.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,11 @@ def _get_private_bytes_from_file(
338338
(type(None), str),
339339
# SNOW-1825621: OAUTH implementation
340340
),
341+
"oauth_credentials_in_body": (
342+
False,
343+
bool,
344+
# SNOW-2300649: Option to send client credentials in body
345+
),
341346
"oauth_authorization_url": (
342347
"https://{host}:{port}/oauth/authorize",
343348
str,
@@ -1312,6 +1317,7 @@ def __open_connection(self):
13121317
host=self.host, port=self.port
13131318
),
13141319
scope=self._oauth_scope,
1320+
credentials_in_body=self._oauth_credentials_in_body,
13151321
connection=self,
13161322
)
13171323
elif self._authenticator == USR_PWD_MFA_AUTHENTICATOR:

test/unit/test_auth_oauth_credentials.py

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,15 @@ def test_auth_oauth_credentials_oauth_type():
2727

2828

2929
@pytest.mark.parametrize(
30-
"authenticator", ["OAUTH_CLIENT_CREDENTIALS", "oauth_client_credentials"]
30+
"authenticator, oauth_credentials_in_body",
31+
[
32+
("OAUTH_CLIENT_CREDENTIALS", True),
33+
("oauth_client_credentials", False),
34+
("Oauth_Client_Credentials", None),
35+
],
3136
)
32-
def test_oauth_client_credentials_authenticator_is_case_insensitive(
33-
monkeypatch, authenticator
37+
def test_oauth_client_credentials_parameters(
38+
monkeypatch, authenticator, oauth_credentials_in_body
3439
):
3540
"""Test that OAuth client credentials authenticator is case insensitive."""
3641
import snowflake.connector
@@ -53,30 +58,59 @@ def mock_post_request(self, url, headers, json_body, **kwargs):
5358

5459
# Mock the OAuth client credentials token request to avoid making HTTP requests
5560
def mock_get_request_token_response(self, connection, fields):
56-
# Simulate successful token retrieval
61+
# Return fields to verify they are set correctly in tests
5762
return (
58-
"mock_access_token",
63+
str(fields),
5964
None,
60-
) # Client credentials doesn't use refresh tokens
65+
)
6166

6267
monkeypatch.setattr(
6368
AuthByOauthCredentials,
6469
"_get_request_token_response",
6570
mock_get_request_token_response,
6671
)
6772

73+
oauth_credentials_in_body_arg = (
74+
{"oauth_credentials_in_body": oauth_credentials_in_body}
75+
if oauth_credentials_in_body is not None
76+
else {}
77+
)
6878
# Create connection with OAuth client credentials authenticator
6979
conn = snowflake.connector.connect(
7080
user="testuser",
7181
account="testaccount",
7282
authenticator=authenticator,
7383
oauth_client_id="test_client_id",
7484
oauth_client_secret="test_client_secret",
85+
**oauth_credentials_in_body_arg,
7586
)
7687

7788
# Verify that the auth_class is an instance of AuthByOauthCredentials
7889
assert isinstance(conn.auth_class, AuthByOauthCredentials)
7990

91+
# Verify that the credentials_in_body attribute is set correctly
92+
expected_credentials_in_body = (
93+
oauth_credentials_in_body if oauth_credentials_in_body is not None else False
94+
)
95+
assert conn.auth_class._credentials_in_body is expected_credentials_in_body
96+
97+
str_fields, _ = conn.auth_class._request_tokens(
98+
conn=conn,
99+
authenticator=authenticator,
100+
account="<unused-acount>",
101+
user="<unused-user>",
102+
service_name=None,
103+
)
104+
credential_fields = (
105+
", 'client_id': 'test_client_id', 'client_secret': 'test_client_secret'"
106+
if expected_credentials_in_body
107+
else ""
108+
)
109+
assert (
110+
str_fields
111+
== "{'grant_type': 'client_credentials', 'scope': ''" + credential_fields + "}"
112+
)
113+
80114
conn.close()
81115

82116

0 commit comments

Comments
 (0)