---
title: Access Snowflake | Keycard
description: Control Snowflake access through Keycard using Okta groups, JWT authentication, and policy-based authorization for AI agents.
---

In this tutorial, you’ll set up end-to-end access control for Snowflake using Keycard. Your AI agents will query Snowflake through a service account, but Keycard governs *who* can access the Snowflake MCP based on Okta group membership, even if those users don’t have direct Snowflake accounts.

## Architecture

User / Agent

MCP token

MCP Server

Snowflake token

Snowflake

KeycardAuth via Okta

KeycardToken exchange

Snowflake has a single service user with multiple roles. Keycard acts as the policy layer: it grants different scopes based on Okta group membership, which determines what the user can do in Snowflake. Users don’t need individual Snowflake accounts; Keycard decides who gets through and with what permissions.

## What you’ll build

By the end of this tutorial, you’ll have:

- Okta configured as an identity provider with a `data-analysts` group
- Keycard policies that grant write access to analysts; everyone else gets read-only
- Snowflake configured with read-only and read-write roles, accessed via a single service user
- An MCP server that connects AI agents to Snowflake with permissions matching their Okta groups

## Prerequisites

Before starting, you’ll need:

- A [Keycard account](https://console.keycard.ai) (sign up free). After signing up, go to **Zone Settings** to find your zone ID (you’ll need this throughout the tutorial)

- An Okta account with admin access, or [sign up for the free Integrator plan](https://developer.okta.com/signup/)

- [Claude Code](https://docs.anthropic.com/en/docs/claude-code) installed

- A [Snowflake account](https://signup.snowflake.com/) with `ACCOUNTADMIN` role (required for creating security integrations)

- [Docker](https://docs.docker.com/get-docker/) and [Docker Compose](https://docs.docker.com/compose/install/) installed

- Clone the tutorial repository:

  Terminal window

  ```
  git clone https://github.com/keycardai/tutorial-snowflake-mcp.git
  cd tutorial-snowflake-mcp
  cp .env.example .env
  ```

### Find your Snowflake account URL

To find your Snowflake account URL:

1. Sign in to [Snowflake](https://app.snowflake.com/)
2. Click your account name in the bottom-left corner
3. Hover over your account and click the **link icon** to copy the account URL

The URL looks like `https://abc12345.us-east-1.snowflakecomputing.com`.

### Prepare your environment file

Open `.env` in your editor. You’ll fill in values as you progress through the tutorial, but start by adding these now:

Terminal window

```
# Keycard zone (from Zone Settings in Keycard Console)
KEYCARD_ZONE_URL=https://<ZONE-ID>.keycard.cloud


# Snowflake connection
SNOWFLAKE_ACCOUNT_URL=<YOUR-SNOWFLAKE-ACCOUNT-URL>
SNOWFLAKE_DATABASE=GREENDALE
SNOWFLAKE_WAREHOUSE=COMPUTE_WH
```

The remaining values (`KEYCARD_CLIENT_ID` and `KEYCARD_CLIENT_SECRET`) will be added in Part 2 when you create the application.

## Part 1: Configure Okta as an Identity Provider

First, we’ll connect Okta to Keycard so users can authenticate with their existing corporate credentials.

1. **Create an OIDC application in Okta**

   In Okta Admin Console, navigate to **Applications > Applications** and click **Create App Integration**.

   Select **OIDC - OpenID Connect** and **Web Application**, then click **Next**.

   Configure the application:

   | Field                     | Value                                                            |
   | ------------------------- | ---------------------------------------------------------------- |
   | **App integration name**  | Keycard                                                          |
   | **Grant type**            | Authorization Code, Refresh Token                                |
   | **Sign-in redirect URI**  | `https://<ZONE-ID>.keycard.cloud/oauth/2/redirect`               |
   | **Sign-out redirect URI** | `https://<ZONE-ID>.keycard.cloud/openid/connect/redirect/logout` |
   | **Controlled access**     | Allow everyone in your organization to access                    |

   Tip

   Find your Zone ID in Keycard Console under **Zone Settings**. The redirect URL is shown there as **OAuth 2 Redirect URL**.

   Click **Save**.

   Caution

   **Copy the Client ID and Client Secret now.** You’ll need these in step 4 when adding Okta as a provider in Keycard.

2. **Configure Okta to send group claims**

   While still on the **Sign On** tab, scroll to **Advanced Settings** and expand **advanced options**.

   Under **Group Claims**, click **Edit** and configure:

   | Field                   | Value                                                  |
   | ----------------------- | ------------------------------------------------------ |
   | **Groups claim type**   | Filter                                                 |
   | **Groups claim filter** | Name: `groups`, Filter: **Matches regex**, Value: `.*` |

   This sends all group memberships in the ID token. For production, narrow the regex to only the groups Keycard needs.

3. **Create an Okta group for analysts**

   In Okta Admin Console, navigate to **Directory > Groups** and create a group:

   | Group Name      | Description                              |
   | --------------- | ---------------------------------------- |
   | `data-analysts` | Full read-write access to Snowflake data |

   Add users who need write access to this group. Users *not* in `data-analysts` automatically get read-only access (no separate group needed).

4. **Add Okta as a provider in Keycard**

   In [Keycard Console](https://console.keycard.ai), navigate to **Providers** and click **Add Provider**.

   | Field             | Value                                                        |
   | ----------------- | ------------------------------------------------------------ |
   | **Name**          | Okta                                                         |
   | **Issuer URL**    | `https://<YOUR-OKTA-DOMAIN>` (e.g., `https://acme.okta.com`) |
   | **Client ID**     | From step 1                                                  |
   | **Client Secret** | From step 1                                                  |

   Click **Create Provider**.

   After creating the provider, click into its settings. Under **Advanced Settings**, add `groups` to **Additional Scopes**. This ensures Keycard requests group claims from Okta during authentication.

5. **Enable Okta for zone sign-in**

   In Keycard Console, navigate to **Zone Settings** and scroll to **Zone sign in configuration**.

   Toggle **Use an external Identity Provider** on and select **Okta** from the dropdown.

   Click **Save Changes**.

Users can now sign in to your Keycard zone using their Okta credentials.

## Part 2: Configure Keycard Resources and Application

Now we’ll register everything in Keycard: the Snowflake API as a resource, the MCP server as a resource, and an application that ties them together.

1. **Create the Snowflake API resource**

   In [Keycard Console](https://console.keycard.ai), navigate to **Resources** and click **Add Resource > Add Manually**.

   | Field                   | Value                          |
   | ----------------------- | ------------------------------ |
   | **Resource Name**       | Snowflake API                  |
   | **Resource Identifier** | `<YOUR-SNOWFLAKE-ACCOUNT-URL>` |
   | **Credential Provider** | Zone Provider                  |

   This resource represents the Snowflake API that the MCP server will call on behalf of users.

2. **Create the Snowflake MCP resource**

   Click **Add Resource > Add Manually** again, this time to register the MCP server that users will authenticate against.

   | Field                   | Value                    |
   | ----------------------- | ------------------------ |
   | **Resource Name**       | Snowflake MCP            |
   | **Resource Identifier** | `http://localhost:3100/` |
   | **Credential Provider** | Zone Provider            |

   Tip

   These two resources serve different purposes:

   - **Snowflake API** is the downstream resource the MCP server calls on behalf of users
   - **Snowflake MCP** is the resource users authenticate against to use the MCP server

   Users request a token for the MCP resource, then the MCP server exchanges that for a Snowflake API token.

   Note

   The Resource Identifier must match where your MCP server runs. For local development, use `http://localhost:3100/`. Update this when deploying to production.

3. **Create the Snowflake MCP application**

   Navigate to **Applications** and click **Add Application > Add Manually**.

   | Field          | Value                    |
   | -------------- | ------------------------ |
   | **Name**       | Snowflake MCP            |
   | **Identifier** | `http://localhost:3100/` |

   Click **Create Application**.

4. **Configure the application resources**

   On the application details page:

   Under **Provides**, click **Add Provided Resource** and select **Snowflake MCP** (the resource your application exposes).

   Under **Dependency**, click **Add Dependency** and select **Snowflake API** (the resource the application accesses on behalf of users).

   Tip

   **Provided vs Dependency**: A *provided resource* is what your application exposes to users (the MCP server’s API). A *dependency* is what your application needs to call on behalf of users (Snowflake). Together, they enable token exchange: when users authenticate to your MCP server, it can exchange their token for a Snowflake token.

5. **Generate client credentials**

   On the application details page, go to the **Application Credentials** tab and click **Add Credential**. In the modal, select **Client ID & Secret**.

   Caution

   **Copy both values now and add them to your `.env` file.** This is the only time you’ll see the Client Secret. If you lose it, you’ll need to generate new credentials.

   - `KEYCARD_CLIENT_ID`
   - `KEYCARD_CLIENT_SECRET`

   Note

   Client credentials are for local development. For production deployments to cloud platforms, you’ll use workload identity federation instead.

6. **Create an access policy**

   Navigate to **Policies > All Policies** and click **Create Policy**.

   Name it **Snowflake MCP**, then switch to the **Cedar** tab and add the following policy:

   ```
   // Allow any user to access the MCP server
   permit (
     principal is Keycard::User,
     action,
     resource
   )
   when
   {
     (resource has identifier) &&
     ((resource.identifier) == "http://localhost:3100/")
   };


   // Allow any user to access Snowflake, unless requesting READWRITE_ROLE
   permit (
     principal is Keycard::User,
     action,
     resource
   )
   when
   {
     (((resource has identifier) && (context has scopes)) &&
      ((resource.identifier) == "<YOUR-SNOWFLAKE-ACCOUNT-URL>")) &&
     (!((context.scopes).containsAny(["session:role:READWRITE_ROLE"])))
   };


   // Allow data-analysts group members to access Snowflake with READWRITE_ROLE
   permit (
     principal is Keycard::User,
     action,
     resource
   )
   when
   {
     ((((((resource has identifier) && (context has scopes)) &&
         (context has subject_claims)) &&
        ((context.subject_claims) has groups)) &&
       ((resource.identifier) == "<YOUR-SNOWFLAKE-ACCOUNT-URL>")) &&
      ((context.scopes).containsAny(["session:role:READWRITE_ROLE"]))) &&
     (((context.subject_claims).groups).contains("data-analysts"))
   };
   ```

   Click **Validate** to check the policy syntax, then click **Publish Policy** to save it.

7. **Create and activate a policy set**

   Go back to **Policies** (this lands you on **Policy Sets**) and click **New Policy Set**.

   Name it **Snowflake Access** and add two policies:

   - **default-app-delegation** (the preconfigured policy that enables token exchange)
   - **Snowflake MCP** (the policy you just created)

   Click **Publish as Candidate** to create a version. This lands you on the candidate page where you can click **Activate** to make it live.

   Note

   The `default-app-delegation` policy allows applications to request tokens on behalf of users. The `Snowflake MCP` policy controls which users can access which resources and roles.

## Part 3: Configure Snowflake for Keycard JWTs

Snowflake needs to trust Keycard as an OAuth authorization server. We’ll create a single service user for the MCP server. Keycard handles user identity and access control, so individual Snowflake accounts aren’t needed.

Note

Snowflake users, roles, databases, and security integrations are account-level objects. If these have already been created (e.g., by another workshop participant), the `CREATE` statements will error. Skip steps that were already completed.

1. **Create the MCP service user**

   In [Snowflake](https://app.snowflake.com/), click **+ Create** in the top-left and select **SQL File**.

   In the top-right of the worksheet, select `ACCOUNTADMIN` as your role and `COMPUTE_WH` as your warehouse.

   Run the following SQL to create a service user and two roles for the MCP server. Replace `<YOUR-KEYCARD-APPLICATION-ID>` with your **Application ID** from the Keycard Console application details page:

   ```
   BEGIN
     -- Create roles with different permission levels
     CREATE ROLE READONLY_ROLE;
     CREATE ROLE READWRITE_ROLE;


     -- Create service user and grant both roles
     CREATE USER MCP_SERVICE_USER
       TYPE = SERVICE
       LOGIN_NAME = '<YOUR-KEYCARD-APPLICATION-ID>'
       DEFAULT_ROLE = READONLY_ROLE
       COMMENT = 'Service account for Keycard MCP server';


     GRANT ROLE READONLY_ROLE TO USER MCP_SERVICE_USER;
     GRANT ROLE READWRITE_ROLE TO USER MCP_SERVICE_USER;
   END;
   ```

   Tip

   The service user defaults to `READONLY_ROLE` for safety. When an analyst needs to write data, the MCP server specifies `READWRITE_ROLE` at connection time, but only if their token includes that scope. Users not in `data-analysts` can’t escalate since their token only has `READONLY_ROLE`.

2. **Create an OAuth security integration**

   Configure Snowflake to accept Keycard-issued JWTs:

   ```
   CREATE SECURITY INTEGRATION keycard_oauth
     TYPE = EXTERNAL_OAUTH
     ENABLED = TRUE
     EXTERNAL_OAUTH_TYPE = CUSTOM
     EXTERNAL_OAUTH_ISSUER = 'https://<ZONE-ID>.keycard.cloud'
     EXTERNAL_OAUTH_JWS_KEYS_URL = 'https://<ZONE-ID>.keycard.cloud/openidconnect/jwks'
     EXTERNAL_OAUTH_AUDIENCE_LIST = ('<YOUR-SNOWFLAKE-ACCOUNT-URL>')
     EXTERNAL_OAUTH_TOKEN_USER_MAPPING_CLAIM = 'client_id'
     EXTERNAL_OAUTH_SNOWFLAKE_USER_MAPPING_ATTRIBUTE = 'LOGIN_NAME'
     EXTERNAL_OAUTH_SCOPE_MAPPING_ATTRIBUTE = 'scope'
     EXTERNAL_OAUTH_SCOPE_DELIMITER = ' '
     EXTERNAL_OAUTH_ANY_ROLE_MODE = 'ENABLE';
   ```

   Note

   The `client_id` claim identifies the MCP application. Snowflake maps this to the service user’s `LOGIN_NAME`. The `scope` claim contains role authorizations like `session:role:READWRITE_ROLE` or `session:role:READONLY_ROLE`.

3. **Seed sample data**

   Create a database, schema, and sample data:

   ```
   BEGIN
     CREATE DATABASE GREENDALE;
     CREATE SCHEMA GREENDALE.ENROLLMENT;


     USE DATABASE GREENDALE;
     USE SCHEMA ENROLLMENT;


     CREATE TABLE STUDENTS (
       ID INT AUTOINCREMENT PRIMARY KEY,
       NAME VARCHAR(100),
       EMAIL VARCHAR(100),
       MAJOR VARCHAR(50),
       GPA DECIMAL(3,2),
       ENROLLED_DATE DATE
     );


     INSERT INTO STUDENTS (NAME, EMAIL, MAJOR, GPA, ENROLLED_DATE) VALUES
       ('Troy Barnes', 'troy.barnes@greendale.edu', 'Air Conditioning Repair', 3.2, '2009-09-01'),
       ('Abed Nadir', 'abed.nadir@greendale.edu', 'Film Studies', 3.8, '2009-09-01'),
       ('Jeff Winger', 'jeff.winger@greendale.edu', 'Undeclared', 2.9, '2009-09-01'),
       ('Britta Perry', 'britta.perry@greendale.edu', 'Psychology', 3.1, '2009-09-01'),
       ('Annie Edison', 'annie.edison@greendale.edu', 'Healthcare Administration', 4.0, '2009-09-01'),
       ('Shirley Bennett', 'shirley.bennett@greendale.edu', 'Business', 3.5, '2009-09-01'),
       ('Pierce Hawthorne', 'pierce.hawthorne@greendale.edu', 'Undeclared', 2.1, '2009-09-01');


     -- READONLY_ROLE: read-only access
     GRANT USAGE ON WAREHOUSE COMPUTE_WH TO ROLE READONLY_ROLE;
     GRANT USAGE ON DATABASE GREENDALE TO ROLE READONLY_ROLE;
     GRANT USAGE ON SCHEMA GREENDALE.ENROLLMENT TO ROLE READONLY_ROLE;
     GRANT SELECT ON ALL TABLES IN SCHEMA GREENDALE.ENROLLMENT TO ROLE READONLY_ROLE;


     -- READWRITE_ROLE: read-write access
     GRANT USAGE ON WAREHOUSE COMPUTE_WH TO ROLE READWRITE_ROLE;
     GRANT USAGE ON DATABASE GREENDALE TO ROLE READWRITE_ROLE;
     GRANT USAGE ON SCHEMA GREENDALE.ENROLLMENT TO ROLE READWRITE_ROLE;
     GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA GREENDALE.ENROLLMENT TO ROLE READWRITE_ROLE;
   END;
   ```

## Part 4: Run the Snowflake MCP Server

In the `tutorial-snowflake-mcp` directory you cloned during prerequisites, run the pre-built MCP server. The server uses the MCP service account you created, but Keycard validates each user’s identity and group membership before allowing access.

1. **Run with Docker**

   Terminal window

   ```
   docker compose up
   ```

   The MCP server is now running at `http://localhost:3100/mcp`.

2. **Add the MCP server to Claude Code**

   In a new terminal, add the Snowflake MCP server to Claude Code:

   Terminal window

   ```
   claude mcp add --transport http snowflake-mcp http://localhost:3100/mcp
   ```

3. **Authenticate with the MCP server**

   Start Claude Code:

   Terminal window

   ```
   claude
   ```

   Type `/mcp` and select the **snowflake-mcp** server. This will open your browser and take you through the Keycard authentication flow (backed by Okta).

4. **Test read-only access**

   Sign in as a user who is *not* in the `data-analysts` group.

   Ask Claude to read data:

   ```
   List all students at Greendale
   ```

   This should succeed: the user has `READONLY_ROLE` access.

   Now ask Claude to write data:

   ```
   Add a new student named Ben Chang with email ben.chang@greendale.edu, major Spanish, GPA 2.5
   ```

   This should fail: `READONLY_ROLE` doesn’t have INSERT permissions.

5. **Test read-write access**

   Add yourself to the `data-analysts` group in Okta, then sign out of Keycard to refresh your session:

   ```
   https://<ZONE-ID>.keycard.cloud/logout
   ```

   In Claude Code, re-authenticate by typing `/mcp`, selecting **snowflake-mcp**, and completing the login flow again.

   Ask Claude to write data:

   ```
   Add a new student named Ben Chang with email ben.chang@greendale.edu, major Spanish, GPA 2.5
   ```

   This should succeed: the user has `READWRITE_ROLE` access with INSERT permissions.

   Verify the insert worked:

   ```
   Show me all students including Ben Chang
   ```

## Next steps

You now have Snowflake connected through Keycard with Okta-based identity and group-based policies. From here, you can:

- Deploy to higher environments with [workload identity federation](/concepts/credentials/#workload-identity/index.md) to eliminate client secrets
- Create per-operation roles (e.g., `INSERT_ROLE`, `UPDATE_ROLE`, `DELETE_ROLE`) for even more granular access control
- Add more granular policies (e.g., restrict specific warehouses or databases)
- Expand the MCP server with additional tools (schema inspection, write operations)
- Set up [audit log export](/admin/audit-log-export/index.md) to track all Snowflake access
- Configure additional identity providers alongside Okta
