OAuth token passthrough to a Cortex Agent: code

Every file in the OAuth token passthrough to a Cortex Agent recipe. To run the recipe, download oauth-token-passthrough.zip, unzip it, and run the scripts from the oauth-token-passthrough/ folder as the recipe describes. Comments in the files that say “see README” refer to the recipe page; the zip includes it as README.md.

client/

client/passthrough_client.py

"""Relay a user's own token to a Cortex Agent (token passthrough).

The relay (a bot, an orchestrator, a web backend) receives the signed-in
user's Entra ID access token and forwards it unchanged as the bearer token on
the agent's :run endpoint. Snowflake validates the token against the External
OAuth security integration, maps its `upn` claim to a Snowflake user, and runs
the agent as that user. The relay holds no Snowflake credential of its own.

Environment:
  SNOWFLAKE_ACCOUNT_URL  https://<account_identifier>.snowflakecomputing.com
  USER_ACCESS_TOKEN      the user's access token (production: taken from the
                         signed-in session; never logged or stored)

Testing the Snowflake side before Entra ID is configured:
  --test-keypair-user USER --test-keypair-file KEY.p8 --account ACCOUNT
  mints a short-lived key-pair JWT for USER and sends it with token type
  KEYPAIR_JWT, so you can prove per-user results with the same request shape.

Usage:
  python passthrough_client.py "How many tickets can I see, and which are open?"
"""

import argparse
import base64
import hashlib
import os
import sys
import time

import requests

AGENT_DB = os.environ.get("AGENT_DATABASE", "COOKBOOK_OAUTH_TOKEN_PASSTHROUGH")
AGENT_SCHEMA = os.environ.get("AGENT_SCHEMA", "APP")
AGENT_NAME = os.environ.get("AGENT_NAME", "SUPPORT_TICKETS_AGENT")


def _require(value, what):
    """Stop with a clear message instead of a KeyError when a setting is missing."""
    if not value:
        sys.exit(f"missing {what}")
    return value


def keypair_jwt(account, user, key_file):
    import jwt
    from cryptography.hazmat.primitives import serialization

    with open(key_file, "rb") as f:
        key = serialization.load_pem_private_key(f.read(), password=None)
    pub = key.public_key().public_bytes(serialization.Encoding.DER, serialization.PublicFormat.SubjectPublicKeyInfo)
    fp = "SHA256:" + base64.b64encode(hashlib.sha256(pub).digest()).decode()
    acct, usr, now = account.upper().split(".")[0], user.upper(), int(time.time())
    return jwt.encode(
        {"iss": f"{acct}.{usr}.{fp}", "sub": f"{acct}.{usr}", "iat": now, "exp": now + 600}, key, algorithm="RS256"
    )


def run_agent(question, token, token_type="OAUTH"):
    account_url = _require(
        os.environ.get("SNOWFLAKE_ACCOUNT_URL"),
        "SNOWFLAKE_ACCOUNT_URL: export https://<account_identifier>.snowflakecomputing.com",
    )
    url = f"{account_url.rstrip('/')}/api/v2/databases/{AGENT_DB}/schemas/{AGENT_SCHEMA}/agents/{AGENT_NAME}:run"
    headers = {
        "Authorization": f"Bearer {token}",
        "X-Snowflake-Authorization-Token-Type": token_type,
        "Content-Type": "application/json",
        "Accept": "application/json",
    }
    body = {"messages": [{"role": "user", "content": [{"type": "text", "text": question}]}], "stream": False}
    return requests.post(url, headers=headers, json=body, timeout=300)


def answer_text(resp_json):
    return "\n".join(b.get("text", "") for b in resp_json.get("content", []) if b.get("type") == "text").strip()


def main():
    p = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
    p.add_argument("question")
    p.add_argument("--test-keypair-user")
    p.add_argument("--test-keypair-file")
    p.add_argument("--account", default=os.environ.get("SNOWFLAKE_ACCOUNT"))
    args = p.parse_args()

    if args.test_keypair_user:
        account = _require(args.account, "--account or SNOWFLAKE_ACCOUNT for the key-pair test")
        key_file = _require(args.test_keypair_file, "--test-keypair-file for the key-pair test")
        token, token_type = keypair_jwt(account, args.test_keypair_user, key_file), "KEYPAIR_JWT"
    else:
        token = _require(os.environ.get("USER_ACCESS_TOKEN"), "USER_ACCESS_TOKEN: the signed-in user's access token")
        token_type = "OAUTH"

    r = run_agent(args.question, token, token_type)
    if r.status_code != 200:
        # 401: token invalid, expired, wrong audience, or no user with that LOGIN_NAME.
        sys.exit(f"HTTP {r.status_code}: {r.text[:500]}")
    data = r.json()
    print(answer_text(data))
    # Log this with your own request context; usage views carry a REQUEST_ID column.
    print(f"\n[request_id={r.headers.get('X-Snowflake-Request-Id')}]")


if __name__ == "__main__":
    main()

client/requirements.txt

cryptography==45.0.7
PyJWT==2.15.1
requests==2.34.2

sql/

sql/01_setup.sql

-- =============================================================================
-- 01_setup.sql  (run as ACCOUNTADMIN)
-- OAuth token passthrough: database, warehouse, roles, and the Snowflake users
-- that Entra ID identities map to.
--
-- Each person who calls the agent needs a Snowflake user whose LOGIN_NAME
-- equals the claim the security integration maps (here the Entra ID `upn`).
-- The three users below stand in for people provisioned by your IdP. Replace
-- the LOGIN_NAME values with real UPNs, or provision users with SCIM instead
-- (see README).
-- Idempotent.
-- =============================================================================

SET cookbook_db   = 'COOKBOOK_OAUTH_TOKEN_PASSTHROUGH';
SET cookbook_wh   = 'COOKBOOK_OAUTH_TOKEN_PASSTHROUGH_WH';
SET admin_role    = 'COOKBOOK_OAUTH_TOKEN_PASSTHROUGH_ADMIN';
SET analyst_role  = 'COOKBOOK_OAUTH_TOKEN_PASSTHROUGH_ANALYST';
SET user_a        = 'COOKBOOK_OAUTH_TOKEN_PASSTHROUGH_ANALYST_A';
SET user_b        = 'COOKBOOK_OAUTH_TOKEN_PASSTHROUGH_ANALYST_B';
SET user_lead     = 'COOKBOOK_OAUTH_TOKEN_PASSTHROUGH_LEAD';
SET upn_a         = 'analyst.a@example.com';   -- replace with the Entra ID UPN
SET upn_b         = 'analyst.b@example.com';   -- replace with the Entra ID UPN
SET upn_lead      = 'support.lead@example.com'; -- replace with the Entra ID UPN
SET my_user       = '"' || CURRENT_USER() || '"';

USE ROLE ACCOUNTADMIN;

CREATE ROLE IF NOT EXISTS IDENTIFIER($admin_role)
  COMMENT = 'Cookbook oauth-token-passthrough: owns the recipe objects.';
GRANT ROLE IDENTIFIER($admin_role) TO USER IDENTIFIER($my_user);
GRANT ROLE IDENTIFIER($admin_role) TO ROLE SYSADMIN;
GRANT DATABASE ROLE SNOWFLAKE.CORTEX_USER TO ROLE IDENTIFIER($admin_role);

CREATE WAREHOUSE IF NOT EXISTS IDENTIFIER($cookbook_wh)
  WAREHOUSE_SIZE = XSMALL AUTO_SUSPEND = 60 AUTO_RESUME = TRUE INITIALLY_SUSPENDED = TRUE
  COMMENT = 'Cookbook oauth-token-passthrough warehouse.';
GRANT OWNERSHIP ON WAREHOUSE IDENTIFIER($cookbook_wh) TO ROLE IDENTIFIER($admin_role) COPY CURRENT GRANTS;

CREATE DATABASE IF NOT EXISTS IDENTIFIER($cookbook_db)
  COMMENT = 'Cookbook oauth-token-passthrough.';
GRANT OWNERSHIP ON DATABASE IDENTIFIER($cookbook_db) TO ROLE IDENTIFIER($admin_role) COPY CURRENT GRANTS;

-- The one role every passthrough user runs as. Identity differs per user;
-- privileges do not. The row access policy in 03 makes results differ.
CREATE ROLE IF NOT EXISTS IDENTIFIER($analyst_role)
  COMMENT = 'Cookbook oauth-token-passthrough: role granted to every mapped user.';
GRANT ROLE IDENTIFIER($analyst_role) TO ROLE IDENTIFIER($admin_role);
GRANT DATABASE ROLE SNOWFLAKE.CORTEX_AGENT_USER TO ROLE IDENTIFIER($analyst_role);

-- Users that Entra ID tokens map to. No password: they authenticate with the
-- IdP token only. DEFAULT_ROLE and DEFAULT_WAREHOUSE are required for agent runs.
CREATE USER IF NOT EXISTS IDENTIFIER($user_a)    TYPE = PERSON COMMENT = 'Cookbook passthrough user: analyst A.';
CREATE USER IF NOT EXISTS IDENTIFIER($user_b)    TYPE = PERSON COMMENT = 'Cookbook passthrough user: analyst B.';
CREATE USER IF NOT EXISTS IDENTIFIER($user_lead) TYPE = PERSON COMMENT = 'Cookbook passthrough user: support lead.';

ALTER USER IDENTIFIER($user_a)    SET LOGIN_NAME = $upn_a    DEFAULT_ROLE = $analyst_role DEFAULT_WAREHOUSE = $cookbook_wh DEFAULT_SECONDARY_ROLES = ();
ALTER USER IDENTIFIER($user_b)    SET LOGIN_NAME = $upn_b    DEFAULT_ROLE = $analyst_role DEFAULT_WAREHOUSE = $cookbook_wh DEFAULT_SECONDARY_ROLES = ();
ALTER USER IDENTIFIER($user_lead) SET LOGIN_NAME = $upn_lead DEFAULT_ROLE = $analyst_role DEFAULT_WAREHOUSE = $cookbook_wh DEFAULT_SECONDARY_ROLES = ();

GRANT ROLE IDENTIFIER($analyst_role) TO USER IDENTIFIER($user_a);
GRANT ROLE IDENTIFIER($analyst_role) TO USER IDENTIFIER($user_b);
GRANT ROLE IDENTIFIER($analyst_role) TO USER IDENTIFIER($user_lead);

-- For the key-pair test in Verify only: lets the admin role set each user's
-- public key, and nothing else on the user.
GRANT MODIFY PROGRAMMATIC AUTHENTICATION METHODS ON USER IDENTIFIER($user_a)    TO ROLE IDENTIFIER($admin_role);
GRANT MODIFY PROGRAMMATIC AUTHENTICATION METHODS ON USER IDENTIFIER($user_b)    TO ROLE IDENTIFIER($admin_role);
GRANT MODIFY PROGRAMMATIC AUTHENTICATION METHODS ON USER IDENTIFIER($user_lead) TO ROLE IDENTIFIER($admin_role);

SHOW USERS LIKE 'COOKBOOK_OAUTH_TOKEN_PASSTHROUGH%';

sql/02_security_integration.sql

-- =============================================================================
-- 02_security_integration.sql  (run as ACCOUNTADMIN; account-level change)
-- External OAuth security integration for Microsoft Entra ID.
--
-- Snowflake validates each bearer token against this integration (issuer,
-- signing keys, audience, expiry), then maps the token's `upn` claim to a
-- Snowflake user's LOGIN_NAME. The session runs as that user.
--
-- Replace the three placeholders (<entra_tenant_id> twice,
-- <snowflake_application_id_uri> once) with the values from your Entra ID app
-- registration first (see README, Prerequisites). Values are case-sensitive,
-- including the trailing slash on the issuer. Snowflake rejects the statement
-- while the keys URL still holds the placeholder, so an unedited script
-- creates nothing. The last query lists any value that still holds one.
--
-- CREATE OR REPLACE, so running this again after you correct a value applies
-- the correction. IF NOT EXISTS would keep the first values. The integration
-- has no grants to keep.
--
-- EXTERNAL_OAUTH_ANY_ROLE_MODE is left at its default (DISABLE): the role comes
-- from the scope in the token, as configured in your Entra ID app registration,
-- and the session cannot switch to another role.
-- =============================================================================

SET integration_name = 'COOKBOOK_OAUTH_TOKEN_PASSTHROUGH_ENTRA';
SET cookbook_wh      = 'COOKBOOK_OAUTH_TOKEN_PASSTHROUGH_WH';

USE ROLE ACCOUNTADMIN;
USE WAREHOUSE IDENTIFIER($cookbook_wh);

CREATE OR REPLACE SECURITY INTEGRATION IDENTIFIER($integration_name)
  TYPE = EXTERNAL_OAUTH
  ENABLED = TRUE
  EXTERNAL_OAUTH_TYPE = AZURE
  EXTERNAL_OAUTH_ISSUER = 'https://sts.windows.net/<entra_tenant_id>/'
  EXTERNAL_OAUTH_JWS_KEYS_URL = 'https://login.microsoftonline.com/<entra_tenant_id>/discovery/v2.0/keys'
  EXTERNAL_OAUTH_AUDIENCE_LIST = ('<snowflake_application_id_uri>')
  EXTERNAL_OAUTH_TOKEN_USER_MAPPING_CLAIM = 'upn'
  EXTERNAL_OAUTH_SNOWFLAKE_USER_MAPPING_ATTRIBUTE = 'login_name'
  COMMENT = 'Cookbook oauth-token-passthrough: Entra ID tokens mapped to Snowflake users by UPN.';

DESCRIBE SECURITY INTEGRATION IDENTIFIER($integration_name);

-- Expect no rows. A row is a placeholder you missed: fix it and run 02 again.
-- Until then no token can validate, because its issuer or audience cannot match.
SELECT "property", "property_value" AS placeholder_still_set
FROM TABLE(RESULT_SCAN(LAST_QUERY_ID()))
WHERE CONTAINS("property_value", '<');

sql/03_data_and_policy.sql

-- =============================================================================
-- 03_data_and_policy.sql  (run as COOKBOOK_OAUTH_TOKEN_PASSTHROUGH_ADMIN)
-- Support tickets, and a fail-closed row access policy on CURRENT_USER().
--
-- With token passthrough, every request runs as the person's own Snowflake
-- user, so CURRENT_USER() identifies them. The policy shows each analyst only
-- the tickets assigned to them, and shows everything to users listed as leads.
-- Anyone else sees nothing. All users share one role; the answer differs only
-- because the identity differs.
--
-- All data is synthetic. Idempotent.
-- =============================================================================

SET cookbook_db = 'COOKBOOK_OAUTH_TOKEN_PASSTHROUGH';
SET cookbook_wh = 'COOKBOOK_OAUTH_TOKEN_PASSTHROUGH_WH';
SET admin_role  = 'COOKBOOK_OAUTH_TOKEN_PASSTHROUGH_ADMIN';

USE ROLE IDENTIFIER($admin_role);
USE WAREHOUSE IDENTIFIER($cookbook_wh);
USE DATABASE IDENTIFIER($cookbook_db);
CREATE SCHEMA IF NOT EXISTS APP;
USE SCHEMA APP;

CREATE TABLE IF NOT EXISTS SUPPORT_TICKETS (
  TICKET_ID     NUMBER      NOT NULL,
  ASSIGNED_TO   VARCHAR     NOT NULL,  -- Snowflake user name of the assignee
  PRIORITY      VARCHAR     NOT NULL,
  STATUS        VARCHAR     NOT NULL,
  CATEGORY      VARCHAR     NOT NULL,
  CREATED_DATE  DATE        NOT NULL,
  RESOLVED_DATE DATE,
  SATISFACTION  NUMBER(2, 1)
)
COMMENT = 'Northwind support tickets (synthetic).';

INSERT OVERWRITE INTO SUPPORT_TICKETS VALUES
  (5001, 'COOKBOOK_OAUTH_TOKEN_PASSTHROUGH_ANALYST_A', 'High',   'Resolved',    'Billing',   '2026-01-05', '2026-01-06', 4.5),
  (5002, 'COOKBOOK_OAUTH_TOKEN_PASSTHROUGH_ANALYST_A', 'Medium', 'Resolved',    'Technical', '2026-01-08', '2026-01-10', 3.8),
  (5003, 'COOKBOOK_OAUTH_TOKEN_PASSTHROUGH_ANALYST_A', 'High',   'Open',        'Technical', '2026-02-02', NULL,         NULL),
  (5004, 'COOKBOOK_OAUTH_TOKEN_PASSTHROUGH_ANALYST_A', 'Low',    'Resolved',    'General',   '2026-02-10', '2026-02-12', 4.2),
  (5005, 'COOKBOOK_OAUTH_TOKEN_PASSTHROUGH_ANALYST_B', 'High',   'Resolved',    'Technical', '2026-01-15', '2026-01-16', 4.8),
  (5006, 'COOKBOOK_OAUTH_TOKEN_PASSTHROUGH_ANALYST_B', 'Medium', 'In Progress', 'Billing',   '2026-02-20', NULL,         NULL),
  (5007, 'COOKBOOK_OAUTH_TOKEN_PASSTHROUGH_ANALYST_B', 'Low',    'Resolved',    'General',   '2026-03-01', '2026-03-04', 3.6),
  (5008, 'UNASSIGNED',                                'High',   'Open',        'Billing',   '2026-03-05', NULL,         NULL);

-- Who sees every ticket. Maintained by the data owner.
CREATE TABLE IF NOT EXISTS TICKET_LEADS (USER_NAME VARCHAR NOT NULL)
  COMMENT = 'Users who see all tickets. Everyone else sees only tickets assigned to them.';
INSERT OVERWRITE INTO TICKET_LEADS VALUES ('COOKBOOK_OAUTH_TOKEN_PASSTHROUGH_LEAD');

CREATE ROW ACCESS POLICY IF NOT EXISTS TICKET_OWNER_POLICY
  AS (assigned_to VARCHAR) RETURNS BOOLEAN -> FALSE
  COMMENT = 'Fail closed: own tickets for the current user, all tickets for leads, nothing otherwise.';

ALTER ROW ACCESS POLICY TICKET_OWNER_POLICY SET BODY ->
  assigned_to = CURRENT_USER()
  OR EXISTS (SELECT 1 FROM COOKBOOK_OAUTH_TOKEN_PASSTHROUGH.APP.TICKET_LEADS l
             WHERE l.USER_NAME = CURRENT_USER());

ALTER TABLE SUPPORT_TICKETS DROP ALL ROW ACCESS POLICIES;
ALTER TABLE SUPPORT_TICKETS ADD ROW ACCESS POLICY TICKET_OWNER_POLICY ON (ASSIGNED_TO);

-- The admin is neither an assignee nor a lead, so it sees nothing.
SELECT COUNT(*) AS tickets_visible_to_admin FROM SUPPORT_TICKETS;

sql/04_agent.sql

-- =============================================================================
-- 04_agent.sql  (run as COOKBOOK_OAUTH_TOKEN_PASSTHROUGH_ADMIN)
-- Semantic view, agent, and the analyst role's grants.
--
-- Object names are fixed and appear as literals in the DDL below. The model is
-- the one value you may change: it lives in the agent specification under
-- models: orchestration.
-- =============================================================================

SET cookbook_db = 'COOKBOOK_OAUTH_TOKEN_PASSTHROUGH';
SET cookbook_wh = 'COOKBOOK_OAUTH_TOKEN_PASSTHROUGH_WH';
SET admin_role  = 'COOKBOOK_OAUTH_TOKEN_PASSTHROUGH_ADMIN';
SET analyst_role = 'COOKBOOK_OAUTH_TOKEN_PASSTHROUGH_ANALYST';

USE ROLE IDENTIFIER($admin_role);
USE WAREHOUSE IDENTIFIER($cookbook_wh);
USE DATABASE IDENTIFIER($cookbook_db);
USE SCHEMA APP;

CREATE OR ALTER SEMANTIC VIEW SUPPORT_TICKETS_SEMANTIC_VIEW
  TABLES (
    tickets AS COOKBOOK_OAUTH_TOKEN_PASSTHROUGH.APP.SUPPORT_TICKETS
      PRIMARY KEY (TICKET_ID)
      COMMENT = 'Support tickets visible to the current user.'
  )
  FACTS (
    tickets.satisfaction AS SATISFACTION COMMENT = 'Customer satisfaction, 1 to 5. NULL until resolved.'
  )
  DIMENSIONS (
    tickets.ticket_id AS TICKET_ID COMMENT = 'Ticket number.',
    tickets.assigned_to AS ASSIGNED_TO COMMENT = 'Assignee.',
    tickets.priority AS PRIORITY COMMENT = 'High, Medium, or Low.',
    tickets.status AS STATUS COMMENT = 'Open, In Progress, or Resolved.',
    tickets.category AS CATEGORY COMMENT = 'Billing, Technical, or General.',
    tickets.created_date AS CREATED_DATE COMMENT = 'Date the ticket was opened, in 2026.',
    tickets.resolved_date AS RESOLVED_DATE COMMENT = 'Date resolved. NULL while open.'
  )
  METRICS (
    tickets.ticket_count AS COUNT(tickets.ticket_id) COMMENT = 'Number of tickets.',
    tickets.avg_satisfaction AS ROUND(AVG(tickets.satisfaction), 1) COMMENT = 'Average satisfaction of resolved tickets.'
  )
  COMMENT = 'Support tickets the current user is allowed to see.';

CREATE OR REPLACE AGENT SUPPORT_TICKETS_AGENT
  COMMENT = 'Support analytics agent called with the end user''s own OAuth token.'
  PROFILE = '{"display_name": "Support Analytics"}'
  COPY GRANTS
  FROM SPECIFICATION
$$
models:
  orchestration: claude-sonnet-4-6   # the model: change it here
orchestration:
  budget:
    seconds: 120
    tokens: 40000
instructions:
  orchestration: Use ticket_analyst for every question about tickets.
  response: >-
    Answer concisely and list ticket numbers when there are five or fewer.
    If no tickets are returned, say that no tickets are visible to this user.
  sample_questions:
    - question: "How many tickets can I see, and which are open?"
tools:
  - tool_spec:
      type: cortex_analyst_text_to_sql
      name: ticket_analyst
      description: Support ticket counts, status, priority, category, dates, and satisfaction.
tool_resources:
  ticket_analyst:
    semantic_view: COOKBOOK_OAUTH_TOKEN_PASSTHROUGH.APP.SUPPORT_TICKETS_SEMANTIC_VIEW
    execution_environment:
      type: warehouse
      warehouse: COOKBOOK_OAUTH_TOKEN_PASSTHROUGH_WH
      query_timeout: 60
$$;

-- One role for every passthrough user. No grants to PUBLIC.
GRANT USAGE  ON DATABASE IDENTIFIER($cookbook_db)                 TO ROLE IDENTIFIER($analyst_role);
GRANT USAGE  ON SCHEMA APP                                         TO ROLE IDENTIFIER($analyst_role);
GRANT USAGE  ON WAREHOUSE IDENTIFIER($cookbook_wh)                 TO ROLE IDENTIFIER($analyst_role);
GRANT USAGE  ON AGENT SUPPORT_TICKETS_AGENT                        TO ROLE IDENTIFIER($analyst_role);
GRANT SELECT ON SEMANTIC VIEW SUPPORT_TICKETS_SEMANTIC_VIEW        TO ROLE IDENTIFIER($analyst_role);
GRANT SELECT ON TABLE SUPPORT_TICKETS                              TO ROLE IDENTIFIER($analyst_role);

SHOW GRANTS TO ROLE IDENTIFIER($analyst_role);

sql/99_teardown.sql

-- =============================================================================
-- 99_teardown.sql  (run as ACCOUNTADMIN)
-- Removes everything the recipe created, including the account-level External
-- OAuth security integration.
-- =============================================================================

SET cookbook_db      = 'COOKBOOK_OAUTH_TOKEN_PASSTHROUGH';
SET cookbook_wh      = 'COOKBOOK_OAUTH_TOKEN_PASSTHROUGH_WH';
SET admin_role       = 'COOKBOOK_OAUTH_TOKEN_PASSTHROUGH_ADMIN';
SET analyst_role     = 'COOKBOOK_OAUTH_TOKEN_PASSTHROUGH_ANALYST';
SET user_a           = 'COOKBOOK_OAUTH_TOKEN_PASSTHROUGH_ANALYST_A';
SET user_b           = 'COOKBOOK_OAUTH_TOKEN_PASSTHROUGH_ANALYST_B';
SET user_lead        = 'COOKBOOK_OAUTH_TOKEN_PASSTHROUGH_LEAD';
SET integration_name = 'COOKBOOK_OAUTH_TOKEN_PASSTHROUGH_ENTRA';

USE ROLE ACCOUNTADMIN;

DROP SECURITY INTEGRATION IF EXISTS IDENTIFIER($integration_name);
DROP DATABASE  IF EXISTS IDENTIFIER($cookbook_db);
DROP WAREHOUSE IF EXISTS IDENTIFIER($cookbook_wh);
DROP USER IF EXISTS IDENTIFIER($user_a);
DROP USER IF EXISTS IDENTIFIER($user_b);
DROP USER IF EXISTS IDENTIFIER($user_lead);
DROP ROLE IF EXISTS IDENTIFIER($analyst_role);
DROP ROLE IF EXISTS IDENTIFIER($admin_role);

SHOW SECURITY INTEGRATIONS LIKE 'COOKBOOK_OAUTH_TOKEN_PASSTHROUGH%';
SHOW DATABASES  LIKE 'COOKBOOK_OAUTH_TOKEN_PASSTHROUGH%';
SHOW WAREHOUSES LIKE 'COOKBOOK_OAUTH_TOKEN_PASSTHROUGH%';
SHOW ROLES      LIKE 'COOKBOOK_OAUTH_TOKEN_PASSTHROUGH%';
SHOW USERS      LIKE 'COOKBOOK_OAUTH_TOKEN_PASSTHROUGH%';