Testing Auth for ISV Integrations
Use this skill when running or designing tests for Databricks partner authentication (PAT, OAuth M2M, U2M).
Core Principle: Clean Environment Per Test
Never run multiple auth types in one process with mixed environment variables. SDKs and drivers error with "more than one authorization method configured" if conflicting credentials are set.
Option A: env -i (Automated Test Runners)
env -i PATH="$PATH" HOME="$HOME" \
DATABRICKS_HOST="https://myworkspace.cloud.databricks.com" \
DATABRICKS_TOKEN="dapi..." \
python your_pat_example.py
Caveat: In long interactive sessions, env -i may strip needed vars like PYTHONPATH. Use Option B for manual testing.
Option B: Fresh Bash Subshell (Interactive Testing)
/bin/bash -c '
export DATABRICKS_HOST="https://myworkspace.cloud.databricks.com"
export DATABRICKS_TOKEN="dapi..."
unset DATABRICKS_CLIENT_ID DATABRICKS_CLIENT_SECRET
export PYTHONUNBUFFERED=1
python your_pat_example.py
'
Starts a completely clean child process. More reliable for interactive manual testing.
Environment Variables by Auth Type
| Auth Type | Selector Value | Required Variables | Must NOT Be Set |
|---|---|---|---|
| PAT | pat |
DATABRICKS_HOST, DATABRICKS_TOKEN |
CLIENT_ID, CLIENT_SECRET |
| OAuth M2M | oauth_m2m |
DATABRICKS_HOST, DATABRICKS_CLIENT_ID, DATABRICKS_CLIENT_SECRET |
DATABRICKS_TOKEN |
| U2M External Browser | u2m_external_browser |
DATABRICKS_HOST |
CLIENT_ID, CLIENT_SECRET, TOKEN |
| U2M Custom OAuth App | u2m_custom_oauth_app |
DATABRICKS_HOST, DATABRICKS_U2M_CLIENT_ID |
DATABRICKS_CLIENT_ID |
| U2M Token-Env | u2m_token_env |
DATABRICKS_HOST, DATABRICKS_ACCESS_TOKEN |
CLIENT_ID, CLIENT_SECRET |
Optional for all: DATABRICKS_HTTP_PATH (SQL drivers), DATABRICKS_WAREHOUSE_ID (Statement Execution API).
Optional for U2M Custom OAuth App: DATABRICKS_U2M_CLIENT_SECRET (if confidential app), DATABRICKS_REDIRECT_URI (default varies by connector).
CLIENT_ID Distinction (Critical)
| Env Var | Auth Type | What It Is |
|---|---|---|
DATABRICKS_CLIENT_ID |
oauth_m2m |
M2M Service Principal UUID |
DATABRICKS_U2M_CLIENT_ID |
u2m_custom_oauth_app |
Custom OAuth App from App Connections |
Using the wrong one causes "OAuth application with client_id not available".
Test Runner Script Pattern
A well-structured test runner:
- Sources credentials once from an env file
- Runs each test with isolated env using
env -iplus only required vars - Records pass/fail per test
- Prints summary at the end
Example Runner Structure
#!/usr/bin/env bash
set -euo pipefail
# Load credentials
source "${1:-credentials.env}"
PASSED=0
FAILED=0
run_test() {
local name="$1"
local cmd="$2"
echo "Running: $name"
if eval "$cmd" > /dev/null 2>&1; then
echo " PASS"
((PASSED++))
else
echo " FAIL"
((FAILED++))
fi
}
# PAT Test
run_test "PAT" "env -i PATH=\"$PATH\" HOME=\"$HOME\" \
DATABRICKS_HOST=\"$DATABRICKS_HOST\" \
DATABRICKS_TOKEN=\"$DATABRICKS_TOKEN\" \
python your_pat_example.py"
# OAuth M2M Test
run_test "OAuth M2M" "env -i PATH=\"$PATH\" HOME=\"$HOME\" \
DATABRICKS_HOST=\"$DATABRICKS_HOST\" \
DATABRICKS_CLIENT_ID=\"$DATABRICKS_CLIENT_ID\" \
DATABRICKS_CLIENT_SECRET=\"$DATABRICKS_CLIENT_SECRET\" \
python your_m2m_example.py"
# Summary
echo ""
echo "Results: $PASSED passed, $FAILED failed"
U2M Browser Test Considerations
Built-in OAuth App (External Browser)
- Do NOT pass M2M credentials (
DATABRICKS_CLIENT_ID/DATABRICKS_CLIENT_SECRET) - The SDK uses the built-in
databricks-cliOAuth app - Redirect URI is typically
http://localhost:8020
env -i PATH="$PATH" HOME="$HOME" \
DATABRICKS_HOST="$DATABRICKS_HOST" \
USER="$USER" DISPLAY="${DISPLAY:-}" \
python your_u2m_browser_example.py
Custom OAuth App (PKCE)
- Use
DATABRICKS_U2M_CLIENT_ID(NOTDATABRICKS_CLIENT_ID) - Optionally set
DATABRICKS_U2M_CLIENT_SECRETandDATABRICKS_REDIRECT_URI - Default redirect URI is typically
http://localhost:8040/callback
env -i PATH="$PATH" HOME="$HOME" \
DATABRICKS_HOST="$DATABRICKS_HOST" \
DATABRICKS_U2M_CLIENT_ID="$DATABRICKS_U2M_CLIENT_ID" \
DATABRICKS_U2M_CLIENT_SECRET="$DATABRICKS_U2M_CLIENT_SECRET" \
DATABRICKS_REDIRECT_URI="http://localhost:8040/callback" \
USER="$USER" DISPLAY="${DISPLAY:-}" \
python your_u2m_custom_app_example.py
Port Cleanup Before U2M Tests
Before running custom OAuth app tests, kill any process holding the redirect port:
lsof -ti:8040 | xargs kill -9 2>/dev/null || true
A previous hung test can leave the port bound.
HTTPServer Pitfalls (PKCE Flow Implementation)
When implementing the local callback server for PKCE:
Python Implementation Pattern
import threading
from http.server import HTTPServer, BaseHTTPRequestHandler
class CallbackHandler(BaseHTTPRequestHandler):
auth_code = None
callback_received = threading.Event()
def do_GET(self):
if "/callback" in self.path:
# Extract code from query params
from urllib.parse import urlparse, parse_qs
query = parse_qs(urlparse(self.path).query)
CallbackHandler.auth_code = query.get("code", [None])[0]
self.send_response(200)
self.end_headers()
self.wfile.write(b"Authentication complete. You can close this tab.")
CallbackHandler.callback_received.set()
else:
# Ignore other requests (favicon, etc.)
self.send_response(204)
self.end_headers()
def log_message(self, format, *args):
pass # Suppress logging
def run_callback_server(port=8040, timeout=120):
server = HTTPServer(("localhost", port), CallbackHandler)
server.timeout = timeout # CRITICAL: Set timeout
# Handle requests until callback received or timeout
while not CallbackHandler.callback_received.is_set():
server.handle_request()
server.server_close() # Use server_close(), NOT shutdown()
return CallbackHandler.auth_code
Key Points:
- Always set
server.timeout— Without it,handle_request()blocks forever - Use
server.server_close()— NOTserver.shutdown()(which hangs withhandle_request()) - Handle multiple requests — Browser may send favicon requests before the callback
- Use
threading.Event— To signal when the actual callback is received
Language-Specific Notes
Python
# PAT
env -i PATH="$PATH" HOME="$HOME" PYTHONUNBUFFERED=1 \
DATABRICKS_HOST="$DATABRICKS_HOST" \
DATABRICKS_TOKEN="$DATABRICKS_TOKEN" \
python your_example.py
# OAuth M2M
env -i PATH="$PATH" HOME="$HOME" PYTHONUNBUFFERED=1 \
DATABRICKS_HOST="$DATABRICKS_HOST" \
DATABRICKS_CLIENT_ID="$DATABRICKS_CLIENT_ID" \
DATABRICKS_CLIENT_SECRET="$DATABRICKS_CLIENT_SECRET" \
python your_example.py
# U2M (browser)
env -i PATH="$PATH" HOME="$HOME" USER="$USER" DISPLAY="${DISPLAY:-}" \
DATABRICKS_HOST="$DATABRICKS_HOST" \
DATABRICKS_U2M_CLIENT_ID="$DATABRICKS_U2M_CLIENT_ID" \
python your_example.py
Java
- Environment isolation: Run with
env -ito prevent Java SDK auto-reading conflicting env vars - Java 17+ JDBC: Add
--add-opens=java.base/java.nio=ALL-UNNAMEDto JVM args for Arrow-based OSS JDBC driver - SDK Workarounds (U2M): Must call
config.setScopes(Arrays.asList("all-apis"))andconfig.resolve()beforeconfig.authenticate()
export MAVEN_OPTS="--add-opens=java.base/java.nio=ALL-UNNAMED"
# PAT
env -i PATH="$PATH" HOME="$HOME" JAVA_HOME="$JAVA_HOME" \
DATABRICKS_HOST="$DATABRICKS_HOST" \
DATABRICKS_TOKEN="$DATABRICKS_TOKEN" \
mvn -q exec:java -Dexec.mainClass="com.example.PatExample"
# OAuth M2M
env -i PATH="$PATH" HOME="$HOME" JAVA_HOME="$JAVA_HOME" \
DATABRICKS_HOST="$DATABRICKS_HOST" \
DATABRICKS_CLIENT_ID="$DATABRICKS_CLIENT_ID" \
DATABRICKS_CLIENT_SECRET="$DATABRICKS_CLIENT_SECRET" \
mvn -q exec:java -Dexec.mainClass="com.example.M2MExample"
Go
- Go SDK uses
APP_AUTH_TYPE: The SDK readsDATABRICKS_AUTH_TYPEinternally, so useAPP_AUTH_TYPEfor your auth selector - Go SQL Driver uses
DATABRICKS_AUTH_TYPE: Safe to use as the selector - Build once, run with
env -i: Build the binary first, then run with isolated env
# Build once (example for Go SDK with build tags)
go build -tags all_auth_example -o ./my_example ./
# PAT (Go SDK uses APP_AUTH_TYPE)
env -i PATH="$PATH" HOME="$HOME" \
APP_AUTH_TYPE=pat \
DATABRICKS_HOST="$DATABRICKS_HOST" \
DATABRICKS_TOKEN="$DATABRICKS_TOKEN" \
./my_example
# OAuth M2M
env -i PATH="$PATH" HOME="$HOME" \
APP_AUTH_TYPE=oauth_m2m \
DATABRICKS_HOST="$DATABRICKS_HOST" \
DATABRICKS_CLIENT_ID="$DATABRICKS_CLIENT_ID" \
DATABRICKS_CLIENT_SECRET="$DATABRICKS_CLIENT_SECRET" \
./my_example
# U2M (browser) - needs USER and DISPLAY
env -i PATH="$PATH" HOME="$HOME" USER="$USER" DISPLAY="${DISPLAY:-}" \
APP_AUTH_TYPE=u2m_custom_oauth_app \
DATABRICKS_HOST="$DATABRICKS_HOST" \
DATABRICKS_U2M_CLIENT_ID="$DATABRICKS_U2M_CLIENT_ID" \
./my_example
Node.js
# PAT
env -i PATH="$PATH" HOME="$HOME" \
DATABRICKS_HOST="$DATABRICKS_HOST" \
DATABRICKS_HTTP_PATH="$DATABRICKS_HTTP_PATH" \
DATABRICKS_TOKEN="$DATABRICKS_TOKEN" \
node your_example.js
# OAuth M2M
env -i PATH="$PATH" HOME="$HOME" \
DATABRICKS_HOST="$DATABRICKS_HOST" \
DATABRICKS_HTTP_PATH="$DATABRICKS_HTTP_PATH" \
DATABRICKS_CLIENT_ID="$DATABRICKS_CLIENT_ID" \
DATABRICKS_CLIENT_SECRET="$DATABRICKS_CLIENT_SECRET" \
node your_example.js
Multi-Auth Example Pattern
Support multiple auth types via an environment variable selector:
import os
auth_type = os.environ.get("APP_AUTH_TYPE", "oauth_m2m")
if auth_type == "pat":
# Use DATABRICKS_TOKEN
token = os.environ["DATABRICKS_TOKEN"]
# Connect with PAT...
elif auth_type == "oauth_m2m":
# Use DATABRICKS_CLIENT_ID and DATABRICKS_CLIENT_SECRET
client_id = os.environ["DATABRICKS_CLIENT_ID"]
client_secret = os.environ["DATABRICKS_CLIENT_SECRET"]
# Connect with M2M...
elif auth_type == "u2m_custom_oauth_app":
# Use DATABRICKS_U2M_CLIENT_ID, run PKCE flow
u2m_client_id = os.environ["DATABRICKS_U2M_CLIENT_ID"]
# Run PKCE flow, get token, connect...
elif auth_type == "u2m_token_env":
# Use pre-obtained token
token = os.environ.get("DATABRICKS_ACCESS_TOKEN") or os.environ.get("DATABRICKS_TOKEN")
# Connect with token...
else:
raise ValueError(f"Unknown auth type: {auth_type}")
Test each auth type:
# PAT
APP_AUTH_TYPE=pat DATABRICKS_HOST=... DATABRICKS_TOKEN=... python all_auth_example.py
# OAuth M2M
APP_AUTH_TYPE=oauth_m2m DATABRICKS_HOST=... DATABRICKS_CLIENT_ID=... DATABRICKS_CLIENT_SECRET=... python all_auth_example.py
# U2M Custom OAuth App
APP_AUTH_TYPE=u2m_custom_oauth_app DATABRICKS_HOST=... DATABRICKS_U2M_CLIENT_ID=... python all_auth_example.py
# U2M Token-Env
APP_AUTH_TYPE=u2m_token_env DATABRICKS_HOST=... DATABRICKS_ACCESS_TOKEN=... python all_auth_example.py
Parallel Test Execution
When running multiple tests in parallel (e.g., different connectors), assign unique callback ports to avoid conflicts:
# Runner 1 uses port 8040
DATABRICKS_REDIRECT_URI="http://localhost:8040/callback" ./run_tests_python.sh &
# Runner 2 uses port 8041
DATABRICKS_REDIRECT_URI="http://localhost:8041/callback" ./run_tests_go.sh &
# Runner 3 uses port 8042
DATABRICKS_REDIRECT_URI="http://localhost:8042/callback" ./run_tests_java.sh &
wait
Validation Tests
Each test should verify both authentication and basic operations:
Validation Pattern
def validate_connection(client):
"""Verify auth works with a simple API call."""
try:
# For SDK: call any API
user = client.current_user.me()
print(f"Authenticated as: {user.user_name}")
return True
except Exception as e:
print(f"Validation failed: {e}")
return False
SQL Driver Validation
def validate_sql_connection(connection):
"""Verify SQL connection works."""
cursor = connection.cursor()
cursor.execute("SELECT 1 AS test")
result = cursor.fetchone()
print(f"Query result: {result}")
cursor.close()
return result[0] == 1
Troubleshooting
| Error | Cause | Solution |
|---|---|---|
| "more than one authorization method configured" | Multiple auth env vars set | Use env -i for clean environment |
| "OAuth application with client_id not available" | Using M2M client_id for U2M | Use DATABRICKS_U2M_CLIENT_ID for U2M |
| "redirect_uri not registered" | URI mismatch | Exact match required in App Connections |
| Port already in use | Previous test hung | lsof -ti:8040 | xargs kill -9 |
| PKCE flow times out | User didn't complete sign-in | Check browser popups, increase timeout |
NullPointerException (Java SDK) |
Missing workarounds | Call setScopes() and resolve() |
Related Skills
- Integration Checklist: integration-checklist.md — Validation requirements
- U2M Patterns: u2m/SKILL.md — Detailed U2M implementation
- Connector Skills: Language-specific auth patterns in
skills/<connector>/SKILL.md