Summary
Adds a native EmbeddedStreamlit helper to the Python connector that generates a
Streamlit-in-Snowflake (SiS) embed URL by calling Snowflake's OAuth 2.0 token-exchange
endpoint (POST /oauth/token) with a service credential and assembling the final embed
URL. This is the native, session-free replacement for the
SYSTEM$STREAMLIT_GENERATE_EMBED_URL SQL system function.
DRAFT — depends on the GlobalServices server-side token-exchange session:streamlit:<fqn>
scope endpoint, which is not yet in production. All tests mock the HTTP layer.
Motivation
Customer backends (e.g. analytics.bmw.com) embed SiS apps into 3rd-party pages and must
mint a short-lived, single-use embed URL from a service credential (PAT / key-pair /
session) without opening an interactive SQL session to call the system function. This
helper performs the token-exchange directly from the driver and never exposes the service
credential to the browser.
Design docs: Streamlit Drivers
and Embedded Streamlit Authentication.
What changed
src/snowflake/connector/embedded_streamlit.py (new): class EmbeddedStreamlit.
src/snowflake/connector/__init__.py: export EmbeddedStreamlit (added to __all__).
test/unit/test_embedded_streamlit.py (new): 29 unit tests, HTTP layer fully mocked.
from snowflake.connector import EmbeddedStreamlit
url = (
EmbeddedStreamlit("MYDB.MYSCHEMA.MY_APP", "https://analytics.example.com")
.prepare(pat="<pat>", account="myaccount") # or conn=..., key_pair=..., or subject_token=...
.get_embed_url()
)
prepare() takes exactly one primary credential source (pat | conn | key_pair |
explicit subject_token), plus account/host/user as needed for credential-only modes,
and an overridable token_endpoint / session_manager for tests. HTTP goes through the
connector's SessionManager; the key-pair JWT is minted via AuthByKeyPair.prepare(account=, user=)
(or a pre-signed JWT may be passed); conn mode pulls the session token and host from the live
SnowflakeConnection (conn.rest.token, conn.host).
Wire contract
POST https://<host>/oauth/token — Content-Type: application/x-www-form-urlencoded; charset=UTF-8,
Accept: application/json, and crucially no Authorization header (the streamlit embed
token-exchange path has no OAuth client identity). Body:
grant_type=urn:ietf:params:oauth:grant-type:token-exchange, subject_token=<credential>,
subject_token_type=<URN>, scope=session:streamlit:<db.schema.app> (FQN contains no :).
subject_token_type mapping (verified against GS SFOAuthTokenType.java):
| Credential |
subject_token_type |
| session |
urn:snowflake:token-type:session |
| PAT |
programmatic_access_token |
| WIF |
urn:snowflake:token-type:wif |
| OAuth access token |
urn:ietf:params:oauth:token-type:access_token |
| key-pair JWT |
urn:ietf:params:oauth:token-type:jwt (provisional, overridable) |
Response 200 { "redirect_uri": "...", "expires_in": <int> }; the single-use code may be a
fragment (#code=) or query (?code= / &code=).
URL assembly reproduces StreamlitGenerateEmbedUrl.java: extract the code (fragment first),
compute the base (scheme://host[:port]/path) minus the code and any echoed
__embeddedApp/__parentOrigin, then build
base + "?__parentOrigin=" + urlencode(parentOrigin) + "&__embeddedApp=true" + "#code=" + code
(joining with & when the base already has a query). The authorization code is read
verbatim (raw split on code=), not form-decoded — the GS system function emits it via
URIBuilder.setFragment("code=" + azCode) with no percent-encoding, and base64url codes can
contain +///=; form-decoding would turn + into a space and corrupt the single-use code.
The credential and the code are treated as secrets and never logged.
Server dependency / open questions
- GS must add
EntityType.STREAMLIT and the session:streamlit:<fqn> scope handler to the
token-exchange path before this works against a real account (not in prod yet).
- The key-pair
subject_token_type is provisional — there is no GS enum value for a
key-pair JWT today; the default urn:ietf:params:oauth:token-type:jwt is overridable, and
an explicit (subject_token, subject_token_type) escape hatch is provided.
- To confirm with GS: the
/oauth/token path and that the single-use azcode is delivered via
redirect_uri (fragment or query) un-percent-encoded — the driver reads the code verbatim,
matching the system function's fragment output.
Testing
test/unit/test_embedded_streamlit.py — 29 tests, HTTP mocked via an injected fake
SessionManager: each credential mode + subject_token_type; scope string; endpoint, headers,
and absence of any Authorization header; default-endpoint-from-account; code extraction from
fragment, query, and &code= with fragment precedence; regression tests that a code with
+///= round-trips verbatim from both fragment and query; URL assembly (parentOrigin
url-encoding, __embeddedApp=true, base-with-query, stripping echoed managed params, evil-origin
negative check); expires_in coercion; and the full error matrix.
Commands run (observed):
PYTHONPATH=src python3 -m pytest test/unit/test_embedded_streamlit.py -q => 29 passed
python3 -m black --safe --check ... => unchanged
python3 -m isort --check-only ... => exit 0
python3 -m flake8 ... => exit 0
Run with PYTHONPATH=src (package not pip-installed here); the benign Failed to import ArrowResult warning is unrelated (Arrow C-ext not compiled). No live integration/e2e — the GS
endpoint is not in production yet.
Out of scope
- Live integration / e2e tests (GS server endpoint not in production).
- Other drivers (Go / .NET / ODBC / JDBC / Node.js — JDBC and Node have sibling draft PRs).
- Removing the
SYSTEM$STREAMLIT_GENERATE_EMBED_URL SQL system function.
🤖 Generated with Claude Code
Summary
Adds a native
EmbeddedStreamlithelper to the Python connector that generates aStreamlit-in-Snowflake (SiS) embed URL by calling Snowflake's OAuth 2.0 token-exchange
endpoint (
POST /oauth/token) with a service credential and assembling the final embedURL. This is the native, session-free replacement for the
SYSTEM$STREAMLIT_GENERATE_EMBED_URLSQL system function.Motivation
Customer backends (e.g.
analytics.bmw.com) embed SiS apps into 3rd-party pages and mustmint a short-lived, single-use embed URL from a service credential (PAT / key-pair /
session) without opening an interactive SQL session to call the system function. This
helper performs the token-exchange directly from the driver and never exposes the service
credential to the browser.
Design docs: Streamlit Drivers
and Embedded Streamlit Authentication.
What changed
src/snowflake/connector/embedded_streamlit.py(new):class EmbeddedStreamlit.src/snowflake/connector/__init__.py: exportEmbeddedStreamlit(added to__all__).test/unit/test_embedded_streamlit.py(new): 29 unit tests, HTTP layer fully mocked.prepare()takes exactly one primary credential source (pat|conn|key_pair|explicit
subject_token), plusaccount/host/useras needed for credential-only modes,and an overridable
token_endpoint/session_managerfor tests. HTTP goes through theconnector's
SessionManager; the key-pair JWT is minted viaAuthByKeyPair.prepare(account=, user=)(or a pre-signed JWT may be passed);
connmode pulls the session token and host from the liveSnowflakeConnection(conn.rest.token,conn.host).Wire contract
POST https://<host>/oauth/token—Content-Type: application/x-www-form-urlencoded; charset=UTF-8,Accept: application/json, and crucially noAuthorizationheader (the streamlit embedtoken-exchange path has no OAuth client identity). Body:
grant_type=urn:ietf:params:oauth:grant-type:token-exchange,subject_token=<credential>,subject_token_type=<URN>,scope=session:streamlit:<db.schema.app>(FQN contains no:).subject_token_typemapping (verified against GSSFOAuthTokenType.java):subject_token_typeurn:snowflake:token-type:sessionprogrammatic_access_tokenurn:snowflake:token-type:wifurn:ietf:params:oauth:token-type:access_tokenurn:ietf:params:oauth:token-type:jwt(provisional, overridable)Response
200 { "redirect_uri": "...", "expires_in": <int> }; the single-use code may be afragment (
#code=) or query (?code=/&code=).URL assembly reproduces
StreamlitGenerateEmbedUrl.java: extract the code (fragment first),compute the base (
scheme://host[:port]/path) minus the code and any echoed__embeddedApp/__parentOrigin, then buildbase + "?__parentOrigin=" + urlencode(parentOrigin) + "&__embeddedApp=true" + "#code=" + code(joining with
&when the base already has a query). The authorization code is readverbatim (raw split on
code=), not form-decoded — the GS system function emits it viaURIBuilder.setFragment("code=" + azCode)with no percent-encoding, and base64url codes cancontain
+///=; form-decoding would turn+into a space and corrupt the single-use code.The credential and the code are treated as secrets and never logged.
Server dependency / open questions
EntityType.STREAMLITand thesession:streamlit:<fqn>scope handler to thetoken-exchange path before this works against a real account (not in prod yet).
subject_token_typeis provisional — there is no GS enum value for akey-pair JWT today; the default
urn:ietf:params:oauth:token-type:jwtis overridable, andan explicit
(subject_token, subject_token_type)escape hatch is provided./oauth/tokenpath and that the single-use azcode is delivered viaredirect_uri(fragment or query) un-percent-encoded — the driver reads the code verbatim,matching the system function's fragment output.
Testing
test/unit/test_embedded_streamlit.py— 29 tests, HTTP mocked via an injected fakeSessionManager: each credential mode +subject_token_type; scope string; endpoint, headers,and absence of any Authorization header; default-endpoint-from-account; code extraction from
fragment, query, and
&code=with fragment precedence; regression tests that a code with+///=round-trips verbatim from both fragment and query; URL assembly (parentOriginurl-encoding,
__embeddedApp=true, base-with-query, stripping echoed managed params, evil-originnegative check);
expires_incoercion; and the full error matrix.Commands run (observed):
Run with
PYTHONPATH=src(package not pip-installed here); the benignFailed to import ArrowResultwarning is unrelated (Arrow C-ext not compiled). No live integration/e2e — the GSendpoint is not in production yet.
Out of scope
SYSTEM$STREAMLIT_GENERATE_EMBED_URLSQL system function.🤖 Generated with Claude Code