avatar
Maximilian Riegler

Grabbing Snowflake Session Tokens for External OAuth

Posted on snowflakeoauthcsharpdotnet

Last summer, I was working on a project that required integrating Snowflake's Cortex Analyst API with external OAuth authentication through Azure AD. The challenge seemed straightforward at first: authenticate users via Azure AD, connect to Snowflake, and make API calls to Cortex Analyst. But I quickly hit a frustrating wall.

The Cortex Analyst API doesn't accept the OAuth access token you use to authenticate. Instead, it expects a Snowflake session token—an internal, short-lived token that the Snowflake driver creates after successful authentication. The problem? This session token is buried deep in the driver's private internal state, with no official API to access it.

After digging through the .NET driver internals, I found a way to extract it using reflection. It's not pretty, it's not officially supported, but it works.

Understanding Session Tokens vs OAuth Tokens

The authentication flow works like this:

  1. You authenticate with Azure AD and get an OAuth access token
  2. You connect to Snowflake using that OAuth token
  3. Snowflake validates your token and creates a secure session
  4. The driver receives a short-lived session token for this specific session
  5. All subsequent requests use this session token, not your OAuth token

For direct API calls to Cortex Analyst, you need to extract that session token from the driver and use it yourself.

The OAuth Setup

For this to work, you need to configure your Azure AD OAuth application with the SESSION:ROLE-ANY scope. This allows you to authenticate initially and then dynamically switch to any role you have access to within Snowflake—critical for multi-tenant scenarios or when users need different permission levels.

Connecting to Snowflake with OAuth is straightforward:

public async Task<SnowflakeDbConnection> GetConnectionAsync(string accessToken, string role)
{
    var connectionString = $"account=YOUR_ACCOUNT;user=YOUR_USERNAME;authenticator=OAUTH;token={accessToken};role={role}";
    var connection = new SnowflakeDbConnection(connectionString);
    await connection.OpenAsync();
    return connection;
}

Now to extract that session token.

Extracting the Session Token with Reflection

The session token lives in a private property called SfSession on the SnowflakeDbConnection object, and within that session object is a private field called sessionToken. Since these are not part of the public API, we need reflection to access them.

Here's the extraction code with proper error handling:

using System.Reflection;
using System.Data;
using Snowflake.Data.Client;

public static string GetSessionToken(SnowflakeDbConnection snowflakeConnection)
{
    if (snowflakeConnection.State != ConnectionState.Open)
    {
        throw new InvalidOperationException("The database connection is not open.");
    }

    // Access the private "SfSession" property
    PropertyInfo sessionProperty = typeof(SnowflakeDbConnection)
        .GetProperty("SfSession", BindingFlags.NonPublic | BindingFlags.Instance);

    if (sessionProperty == null)
    {
        throw new NotSupportedException(
            "Cannot find the internal 'SfSession' property. The Snowflake driver version may have changed.");
    }

    var sessionObject = sessionProperty.GetValue(snowflakeConnection);
    if (sessionObject == null)
    {
        throw new InvalidOperationException("Session object is null.");
    }

    // Extract the "sessionToken" field from the session object
    var tokenField = sessionObject.GetType()
        .GetField("sessionToken", BindingFlags.NonPublic | BindingFlags.Instance);

    if (tokenField == null)
    {
        throw new NotSupportedException(
            "Cannot find the internal 'sessionToken' field. The Snowflake driver version may have changed.");
    }

    var token = tokenField.GetValue(sessionObject) as string;

    if (string.IsNullOrEmpty(token))
    {
        throw new InvalidOperationException("Session token is null or empty.");
    }

    return token;
}

The error handling is crucial here because if Snowflake ever changes their internal driver structure (which they have every right to do), this code will break. The exceptions make it clear what went wrong and where.

Using the Session Token with Cortex Analyst

Once you have the session token, you need to use a specific authorization header format that's barely documented. Instead of the standard Bearer token format, you use Snowflake Token=:

using System.Net.Http;
using System.Text;
using System.Text.Json;

// Configure HttpClient with the session token
_httpClient.DefaultRequestHeaders.Clear();
_httpClient.DefaultRequestHeaders.Add("Authorization", $"Snowflake Token=\"{sessionToken}\"");
_httpClient.DefaultRequestHeaders.Add("Content-Type", "application/json");

// Make a Cortex Analyst API call
var requestBody = new
{
    messages = new[]
    {
        new { role = "user", content = "Show me sales data for the last quarter" }
    },
    semantic_model = "your_semantic_model_name"
};

var json = JsonSerializer.Serialize(requestBody);
var content = new StringContent(json, Encoding.UTF8, "application/json");

var response = await _httpClient.PostAsync(
    "https://YOUR_ACCOUNT.snowflakecomputing.com/api/v2/cortex/analyst/message",
    content);

Important Warnings

This approach comes with significant caveats:

This is not a public API. The SfSession property and sessionToken field are internal implementation details of the Snowflake .NET driver. Snowflake can (and likely will) change this structure in future driver versions without warning, breaking this code.

Consider this a temporary workaround. If Snowflake provides official support for delegated authentication with external OAuth for Cortex Analyst in the future, you should migrate to that immediately.

Version compatibility matters. This code was tested with Snowflake.Data version 4.7.0. Different versions may have different internal structures.

Error handling is critical. The reflection-based approach needs robust error handling to fail gracefully when the driver structure changes.

That said, for scenarios where you need this functionality today and Snowflake hasn't provided an official solution, this workaround gets the job done.