snowflakedb / snowflakedb/snowflake-connector-python

SNOW-3557849: `AuthByOauthCredentials` does not send `audience` in token request body, breaking Auth0 compatibility

Open
#2,880 2 comments 0 reactions 1 assignee View on GitHub

Nobody has claimed this yet.

bug status-triage_done
Dominant language
Python
Stars
730
Forks
574
Avg merge
5h 45m
Merged PRs (30d)
16

Description

Python version

Python 3.12.3

Operating system and processor architecture

macOS-26.5-arm64-arm-64bit

Installed packages
asn1crypto==1.5.1
cryptography==46.0.4
PyJWT==2.11.0
requests==2.32.5
snowflake-connector-python==4.2.0
snowflake-ml-python==1.39.0
snowflake-snowpark-python==1.45.0
urllib3==2.6.3
What did you do?
"""
  Run against any Auth0 Machine-to-Machine app:
    export AUTH0_CLIENT_ID=...
    export AUTH0_CLIENT_SECRET=...
    export AUTH0_TOKEN_URL=https://<tenant>.auth0.com/oauth/token
    export AUTH0_AUDIENCE=<api-identifier>
  """
  import base64, inspect, os, urllib3
  from snowflake.connector.auth.oauth_credentials import AuthByOauthCredentials
  
  # 1) Show the connector's source — `audience` is not in the request body.
  print(inspect.getsource(AuthByOauthCredentials._request_tokens))

  client_id     = os.environ["AUTH0_CLIENT_ID"]
  client_secret = os.environ["AUTH0_CLIENT_SECRET"]
  token_url     = os.environ["AUTH0_TOKEN_URL"]
  audience      = os.environ["AUTH0_AUDIENCE"]
  
  # 2) Try the connector against a real Auth0 tenant.
  auth = AuthByOauthCredentials(
      application="repro",
      client_id=client_id,
      client_secret=client_secret,
      token_request_url=token_url,
      scope=audience, 
  )
  try:
      auth._request_tokens(conn=None, authenticator="oauth_client_credentials",
                           service_name=None, account="dummy", user="dummy")
  except Exception as e: 
      print(f"FAILED: {type(e).__name__}: {e}")

  # 3) Same request, with `audience` added — succeeds.
  resp = urllib3.PoolManager().request_encode_body(
      "POST", token_url,
      headers={
          "Authorization": "Basic " + base64.b64encode(f"{client_id}:{client_secret}".encode()).decode(),
          "Accept": "application/json",
          "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
      },
      encode_multipart=False,
      fields={"grant_type": "client_credentials", "audience": audience},
  )
  print(f"Direct call status: {resp.status}")
  print(f"Direct call body: {resp.data.decode()[:200]}")
What did you expect to see?

EXPECTED: AuthByOauthCredentials._request_tokens mints a token (step 2 of the repro succeeds).
ACTUAL: Step 2 fails with KeyError: 'access_token' because Auth0 returned
{"error":"access_denied","error_description":"..."} due to missing audience parameter.
Step 3 of the repro proves the same request succeeds when audience is included in the body.

ROOT CAUSE:
_request_tokens builds the request body with grant_type, scope, and conditionally client_id/client_secret,
but never includes audience. Auth0 requires audience to determine which API the token is for
(https://auth0.com/docs/api/authentication#client-credentials-flow). Confirmed the same gap exists on main:
https://github.com/snowflakedb/snowflake-connector-python/blob/main/src/snowflake/connector/auth/oauth_credentials.py

This makes oauth_client_credentials unusable with Auth0 as an IdP without monkey-patching the connector.
Other IdPs that route by scope (e.g. Okta) are unaffected.

SUGGESTED FIX:
Accept an optional audience constructor argument and include it in the request body when set:

  fields = {"grant_type": "client_credentials", "scope": self._scope}
  if self._audience is not None:
      fields["audience"] = self._audience
  if self._credentials_in_body:
      fields["client_id"] = self._client_id
      fields["client_secret"] = self._client_secret
  return self._get_request_token_response(conn, fields)

Expose audience (e.g. oauth_audience) as a connection parameter so it can be set via Session.builder.configs(...) / connect(...).

Can you set logging to DEBUG and collect the logs
  2026-05-20 20:19:52,874 - MainThread oauth_credentials.py:60 - _request_tokens() - DEBUG - authenticating with OAuth client credentials flow
  2026-05-20 20:19:53,248 - MainThread connectionpool.py:544 - _make_request() - DEBUG - https://<REDACTED-TENANT>.us.auth0.com:443 "POST /oauth/token ****" 200 N
  2026-05-20 20:19:53,248 - MainThread _oauth_base.py:397 - _get_request_token_response() - DEBUG - OAuth IdP response received, try to parse it
  2026-05-20 20:19:53,248 - MainThread _oauth_base.py:406 - _get_request_token_response() - ERROR - oauth response invalid, does not contain 'access_token'
  2026-05-20 20:19:53,248 - MainThread _oauth_base.py:407 - _get_request_token_response() - DEBUG - received the following response body when requesting oauth token:
  MaskedMessageData(is_masked=False, masked_text='b\'{"error":"access_denied","error_description":"Client \\"<REDACTED_CLIENT_ID>\\" is not authorized to access resource server
  \\"https://azure-latest.relationalai.com\\". You need to create a \\"client-grant\\" associated to this API."}\'', error_str=None)

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.