User-defined functions (UDFs) and stored procedures often need to reach beyond Snowflake. You might enrich rows with data from a public or private endpoint, push results to a ticketing system or pull records from a partner service. External network access makes this possible. You define which hosts your code can reach, and you store the credentials it needs as Snowflake secrets. Your handler reads them at runtime through the SnowflakeSecrets API, so no credential has to be hardcoded.
Real-world handlers rarely run on a single thread. A background thread keeps an OAuth token fresh while a large query processes millions of rows. A thread pool calls an API in parallel. An AWS, Kafka or HTTP client library authenticates on its own internal threads. Until now, secrets had to be read on the handler's main thread. To use these patterns, you had to fetch every credential up front and pass it to the threads that needed it.
Starting now, the following secret types can be read directly from child threads and forked processes: generic strings, username/password, WIF and OAuth tokens. This works in both Python and Java handlers.
This post covers the patterns this unlocks. It then walks through a Java UDF that uses a background thread to keep a Spotify OAuth token fresh while it looks up the top tracks for each artist in a table.
Use cases
The old restriction forced you to read every secret on the main thread and pass it around. With that gone, a few patterns become straightforward:
- Pulling data for hours without token expiry interruptions easily. Keep credentials fresh on long-running jobs by having a background thread which periodically calls
getOAuthAccessToken(or another secret API) so tokens do not expire mid-query. This is the most common case. - Third-party software development kits (SDKs) that spawn their own threads: This is probably the biggest practical one. HTTP clients, AWS/GCP/Azure SDKs, Kafka clients, JDBC pools and OAuth libraries often refresh tokens or run I/O on an internal executor.
- Authentication from worker threads: Parallel HTTP calls, thread pools, or
CompletableFuturework can each fetch the secret they need instead of sharing a value captured on the main thread. - Bookkeeping and side channels: A daemon thread that logs, retries, or talks to a second service can authenticate on its own, without blocking the handler’s main path.
- Fan out to several destinations with different secrets: One handler talks to CRM, ticketing, and a warehouse API in parallel, each with its own secret. Worker threads should each call
SnowflakeSecretsfor the credential they need rather than the main thread preloading every secret and passing a bag of credentials around.
The following secret types work from child threads and forked processes: generic strings, username/password, WIF and OAuth.
Example
Let’s look at an example. Suppose that we want to create an external access UDF that connects to Spotify and analyzes the top tracks for artists.
We first register an app on the Spotify Developer Dashboard to get CLIENT_ID and CLIENT_SECRET.
Step 1: Create a security integration
CREATE OR REPLACE SECURITY INTEGRATION spotify_translate_oauth
TYPE = API_AUTHENTICATION
AUTH_TYPE = OAUTH2
OAUTH_CLIENT_ID = '<client id>'
OAUTH_CLIENT_SECRET = '<client secret>'
OAUTH_TOKEN_ENDPOINT = 'https://accounts.spotify.com/api/token'
OAUTH_AUTHORIZATION_ENDPOINT = 'https://accounts.spotify.com/authorize'
ENABLED = TRUE;Step 2: Create a secret
CREATE OR REPLACE SECRET oauth_token
TYPE = oauth2
API_AUTHENTICATION = spotify_translate_oauth;Execute the SYSTEM$START_OAUTH_FLOW function to retrieve a URL which you can use to complete the OAuth2 consent process.
CALL SYSTEM$START_OAUTH_FLOW( 'my_db.secret_schema.oauth_token' );Then use the SYSTEM$FINISH_OAUTH_FLOW to finish it by following the instructions after completing the OAuth2 consent.
CALL SYSTEM$FINISH_OAUTH_FLOW('<query_string>');Step 3: Create an external access network rule
We create a network rule as follows:
CREATE OR REPLACE NETWORK RULE spotify_apis_network_rule
MODE = EGRESS
TYPE = HOST_PORT
VALUE_LIST = ('api.spotify.com');Step 4: Create an external access integration
CREATE OR REPLACE EXTERNAL ACCESS INTEGRATION spotify_apis_access_integration
ALLOWED_NETWORK_RULES = (spotify_apis_network_rule)
ALLOWED_AUTHENTICATION_SECRETS = (oauth_token)
ENABLED = TRUE;Step 5: Create a UDF
We create a Java UDF to fetch an artist's most popular tracks. In this example we have a background thread that gets the OAuth token periodically to keep it fresh in case of a long running query.
Now we create a Java UDF where we access the secret in a child thread.
create or replace table artists (name string);
insert into artists values ('Radiohead'), ('Miles Davis'), ('Beyoncé');
create or replace function spotify_artist_tracks(artist_name string)
returns string
language java
runtime_version = '21'
returns null on null input
handler = 'SpotifyArtistTracks.execute'
packages = ('com.snowflake:snowpark:latest')
secrets = ('cred' = oauth_token)
external_access_integrations = (spotify_apis_access_integration)
as
$$
import com.fasterxml.jackson.databind.JsonNode;
import com.snowflake.snowpark_java.types.*;
import java.io.IOException;
import java.net.*;
import java.net.http.*;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.concurrent.*;
public class SpotifyArtistTracks {
private static final String API = "https://api.spotify.com/v1";
private final SnowflakeSecrets secrets = SnowflakeSecrets.newInstance();
private final HttpClient client =
HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10)).build();
private final HttpRequest.Builder template =
HttpRequest.newBuilder().timeout(Duration.ofSeconds(10)).GET();
private volatile String accessToken;
// Runs once per handler instance. execute() below runs once per row.
public SpotifyArtistTracks() {
accessToken = secrets.getOAuthAccessToken("cred");
ScheduledExecutorService refresher = Executors.newSingleThreadScheduledExecutor(
r -> { Thread t = new Thread(r); t.setDaemon(true); return t; });
refresher.scheduleAtFixedRate(() -> {
try {
accessToken = secrets.getOAuthAccessToken("cred");
} catch (Exception keepPolling) {
}
}, 1, 1, TimeUnit.MINUTES);
}
public String execute(String artistName) throws IOException, InterruptedException {
JsonNode items = get("/search?type=track&limit=10&q=" + enc("artist:" + artistName))
.path("tracks").path("items");
return items.isArray() && items.size() > 0 ? items.toString() : null;
}
private JsonNode get(String path) throws IOException, InterruptedException {
HttpResponse<String> r = client.send(
template.copy().uri(URI.create(API + path))
.header("Authorization", "Bearer " + accessToken).build(),
HttpResponse.BodyHandlers.ofString());
if (r.statusCode() != 200) {
throw new IOException("Spotify returned " + r.statusCode() + ": " + r.body());
}
return new Variant(r.body()).asJsonNode();
}
private static String enc(String s) { return URLEncoder.encode(s, StandardCharsets.UTF_8); }
}
$$;We call the UDF:
select a.name,
t.value:name::string as track_name,
t.value:album.name::string as album_name
from artists a,
lateral flatten(input => parse_json(spotify_artist_tracks(a.name)), outer => true) t;
Even if the query runs for a long time, the background thread will be able to refresh the OAuth token for continued access to the API.
Getting Started
External Access now supports accessing secret types—including generic strings, username/password credentials, WIF and OAuth tokens — from child threads and processes. This unlocks more flexible application patterns, such as refreshing OAuth tokens in the background, authenticating parallel API calls independently and integrating with third-party libraries that manage their own worker threads.
With this expanded support, UDFs and stored procedures can use concurrent architectures while keeping credentials securely managed through Snowflake. To get started, see Snowflake network external access.
Disclaimer: This example is shown for illustration only. Following along may require additional rights to third-party data, products, or services that are not owned or provided by Snowflake, and because those third-party services may change, Snowflake does not guarantee the accuracy of this example.