Securing the Mesh: Identity Propagation in Enterprise MCP Servers with Snowflake and OAuth
The Model Context Protocol (MCP) is rapidly changing how we connect LLM agents to our data and tools. At Red Hat, we are leaning heavily into this ecosystem to empower our internal teams. However, in an enterprise environment, "connecting data" is never as simple as spinning up a server and exposing an API key.
When we set out to build an MCP server that allows agents (like Cursor, Claude, or custom internal bots) to query our internal Snowflake Data Mesh, we hit a critical wall: Security.
Specifically, how do we allow an LLM agent to execute SQL queries while strictly enforcing our complex, existing Role-Based Access Control (RBAC)? We couldn't use a generic service account; we needed Identity Propagation.
This post details how we architected a secure MCP server using a custom OAuth Proxy layer and Snowflake External OAuth to bring "Chat with your Data" to the enterprise without compromising security.
The Context: Red Hat's Data Mesh
Red Hat manages its enterprise data using a Data Mesh architecture on Snowflake. We treat data as products—specific business domains like Workday Data, Bookings Data, or Customer Telemetry are encapsulated as distinct data products.
Security is paramount here:
- Authentication: Handled via Red Hat IAM (SSO).
- Authorization: Managed via "Rover" user groups. Each data product has strict role policies.
- Access: Users execute queries under their specific persona. If you don't have access to HR data in real life, your AI agent shouldn't have access to it either.
The Challenge: The "God Mode" Problem
We built an MCP Server capable of taking a natural language query, reasoning over it, generating SQL, and executing it on Snowflake.
The naive approach would be to give the MCP Server a "Service Account" with broad access.
- The Problem: If User A asks a question about sensitive HR data, and the Service Account has access, the Agent answers—even if User A shouldn't see that data.
- The Requirement: The SQL query must be executed as the user, not as the server.
We needed a flow where the MCP Client (the Agent) authenticates the human user, retrieves a token, and the MCP Server passes that token to Snowflake to say: "Execute this query on behalf of [user@redhat.com]."
The Solution Architecture
To solve this, we implemented a chain of trust involving a Proxy OAuth Layer and Snowflake External OAuth.
Here is the high-level architecture:
- Snowflake trusts our Red Hat IdP.
- Red Hat IdP trusts our MCP Server.
- The MCP Server acts as an OAuth Proxy to handle Dynamic Client Registration for various agents.
Step 1: Configuring the Trust (Snowflake & IdP)
First, we had to ensure that Snowflake could cryptographically verify the identity of the user coming from our IdP.
On the IdP Side:
We configured our Red Hat IAM connection to include a specific custom claim required by Snowflake (usually the user's email or a specific subject ID) and set the audience to the Snowflake account URL.
On the Snowflake Side: We registered the Red Hat OAuth Server as a trusted Security Integration. This tells Snowflake: "If you see a JWT signed by this Issuer, trust the claims inside it."
-- Snowflake Security Integration Example
CREATE OR REPLACE SECURITY INTEGRATION REDHAT_MCP_OAUTH
TYPE = EXTERNAL_OAUTH
ENABLED = TRUE
EXTERNAL_OAUTH_TYPE = OKTA -- or GENERIC_OAUTH / CUSTOM
EXTERNAL_OAUTH_ISSUER = 'https://sso.redhat.com/auth/...'
EXTERNAL_OAUTH_JWS_KEYS_URL = 'https://sso.redhat.com/.../keys'
EXTERNAL_OAUTH_AUDIENCE_LIST = ('https://<account>.snowflakecomputing.com')
EXTERNAL_OAUTH_TOKEN_USER_MAPPING_CLAIM = 'email' -- Mapping claim
EXTERNAL_OAUTH_SNOWFLAKE_USER_MAPPING_ATTRIBUTE = 'email';
Step 2: The Proxy OAuth Layer (The Innovation)
This was the critical architectural decision.
If we allowed every single MCP Client (every developer's local Cursor instance, every custom agent) to register directly with our corporate IdP, we would overwhelm the central IT systems with thousands of client registrations.
Instead, we built a Proxy OAuth Server inside our MCP application.
- Static Registration: The MCP Server maintains a single, verified 1-to-1 connection (Client ID/Secret) with the Red Hat IdP.
- Dynamic Registration: The MCP Server implements the OAuth 2.1 Dynamic Client Registration spec.
How it works:
- When a user adds our MCP to their client (e.g., Claude Desktop), the client requests registration.
- Our MCP Proxy issues a
client_idandclient_secretto that specific agent instance. - This keeps the "noise" of ephemeral clients contained within our MCP layer, while the actual authentication still rolls up to the trusted corporate IdP.
Step 3: The Execution Flow
Here is the life-cycle of a query:
- Auth Initiation: The user clicks "Connect" in their MCP Client.
- Proxy Handshake: The Client talks to our Proxy Auth Server. The Proxy redirects the user to the Red Hat Login page.
- SSO: The user logs in with their Red Hat credentials.
- Token Minting: Red Hat IdP issues a token (containing the Snowflake claims).
- Token Passthrough: The Proxy receives this token and passes it securely back to the MCP Client.
- The Request: The user asks: "Show me the latest booking numbers."
- Identity Propagation: The MCP Client sends the prompt plus the access token to the MCP Server.
- Execution: The MCP Server connects to Snowflake, attaching the user's token to the connection string.
Here is a simplified Python snippet using the Snowflake Connector showing how we utilize the OAuth token:
import snowflake.connector
def execute_query_as_user(user_oauth_token, sql_query):
"""
Connects to Snowflake using the passed OAuth token.
Snowflake will validate the token and enforce RBAC based on the user's identity.
"""
try:
ctx = snowflake.connector.connect(
account='redhat_account_locator',
authenticator='oauth',
token=user_oauth_token, # The token from Red Hat IdP
warehouse='COMPUTE_WH',
database='DATA_MESH_DB'
)
cs = ctx.cursor()
cs.execute(sql_query)
return cs.fetchall()
except Exception as e:
# If the user doesn't have RBAC access to the specific table,
# Snowflake throws an Authorization error here.
return f"Authorization Error: {str(e)}"
Why This Matters
This architecture provided three massive wins for our enterprise ecosystem:
1. Zero Trust Security
The MCP Server technically has no access to the data. It cannot query Snowflake on its own. It requires a user token to function. If a bad actor compromised the MCP Server, they still couldn't drain the database without valid user credentials.
2. Centralized RBAC
We didn't have to rebuild permission logic in Python. Snowflake remains the "Single Source of Truth" for authorization. If we remove a user from a Rover group in IT, their MCP Agent immediately loses access to that data product.
3. Scalability via Proxying
By implementing the Proxy OAuth layer, we decoupled the explosion of AI agents from our core Identity infrastructure. We can support thousands of distinct agents without needing thousands of tickets filed with IT for OAuth app registration.
Conclusion
As we move from "Chatbots" to "Agents" that perform actions, Identity Propagation becomes the cornerstone of security. By leveraging OAuth 2.1 standards and Snowflake's external security integrations, we enabled a seamless, secure "Chat with your Data" experience at Red Hat.
The future of Enterprise AI isn't just about smarter models—it's about smarter integration.
