Skip to main content

databricks-isv-u2m

U2M (user-to-machine) OAuth flows for Databricks ISV integrations: external-browser, custom-oauth-app, token-env. Covers Python, Java, Go, and Node.js implementations. Use when implementing or debugging browser-based or pre-obtained token auth.

설치로 이동

소스 정보

저장소
databricks-solutions/partner-ai-dev-kit
최근 소스 활동
2026년 4월 15일 00:36
감지된 SKILL.md 언어
영어
스타
5
포크
1

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
name
databricks-isv-u2m
description
U2M (user-to-machine) OAuth flows for Databricks ISV integrations: external-browser, custom-oauth-app, token-env. Covers Python, Java, Go, and Node.js implementations. Use when implementing or debugging browser-based or pre-obtained token auth.
<!-- skill-version: 1.0.0 --> # U2M (User-to-Machine) Authentication (ISV) Use this skill when implementing or testing **U2M** (browser or pre-obtained token) authentication for Databricks partner integrations. ## Important: M2M client_id ≠ U2M OAuth app - The **service principal** client_id used for **OAuth M2M** (`DATABRICKS_CLIENT_ID`) is **not** registered for the authorization code (browser) flow. Using it with the authorize URL or with SDK external-browser causes: "OAuth application with client_id: '...' not available in Databricks account". - For **browser-based U2M** use either: (1) SDK external-browser with **no** client_id (Databricks built-in app), or (2) a **separate** custom OAuth app (created in the account) with a redirect URI. - We use **separate env var names** for U2M custom apps: `DATABRICKS_U2M_CLIENT_ID` and `DATABRICKS_U2M_CLIENT_SECRET` to avoid conflicts with M2M credentials (`DATABRICKS_CLIENT_ID` / `DATABRICKS_CLIENT_SECRET`) in the same env file. ## Three Flows | Flow | When to Use | Required Variables | |------|-------------|---------------------| | **external-browser** | No custom OAuth app; SDK opens browser | `DATABRICKS_HOST` only | | **custom-oauth-app** | Your registered OAuth app + configurable redirect URI | `DATABRICKS_HOST`, `DATABRICKS_U2M_CLIENT_ID`; optional redirect_uri, client_secret | | **token-env** | Pre-obtained token (hosted callback, refresh, CLI) | `DATABRICKS_HOST`, `DATABRICKS_ACCESS_TOKEN` or `DATABRICKS_TOKEN` | --- ## Flow 1: External-Browser (SDK Built-in App) Use the SDK's built-in OAuth app (`databricks-cli`). Do **not** set or pass `DATABRICKS_CLIENT_ID` / `DATABRICKS_CLIENT_SECRET`. ### Python SDK ```python from databricks.sdk import WorkspaceClient from databricks.sdk.config import Config config = Config( host="https://myworkspace.cloud.databricks.com", auth_type="external-browser" ) # Unset M2M credentials before this if they exist in environment client = WorkspaceClient(config=config) # Get token for other uses (e.g., REST API, JDBC) token = config.authenticate()["Authorization"].replace("Bearer ", "") ``` ### Java SDK ```java import com.databricks.sdk.core.DatabricksConfig; import java.util.Arrays; import java.util.Map; public static String getTokenViaExternalBrowser(String host) { String normalizedHost = host.startsWith("https://") ? host : "https://" + host; DatabricksConfig config = new DatabricksConfig() .setHost(normalizedHost) .setAuthType("external-browser") .setScopes(Arrays.asList("all-apis")) // REQUIRED .setOAuthRedirectUrl("http://localhost:8020"); // REQUIRED for built-in app config.resolve(); // REQUIRED: initializes HTTP client Map<String, String> headers = config.authenticate(); return headers.get("Authorization").substring("Bearer ".length()); } ``` ### Token Caching The SDK caches tokens. To force a fresh login: - **Python SDK:** Delete `~/.databricks/token-cache.json` - **Java SDK:** Delete `~/.config/databricks-sdk-java/oauth/` - Also clear browser cookies for the workspace and SSO provider ### Redirect URI for Built-in App | SDK | Default | Registered for `databricks-cli` | Action | |-----|---------|--------------------------------|--------| | Python SDK | `http://localhost:8020` | `http://localhost:8020` | None needed | | Java SDK v0.54.0 | `http://localhost:8080/callback` | `http://localhost:8020` | **Must set explicitly** | --- ## Flow 2: Custom-OAuth-App (Authorization Code + PKCE) Register a custom OAuth app in Databricks and implement PKCE flow. ### Custom OAuth App Setup (Prerequisite) 1. Log in to Databricks account console (`https://accounts.cloud.databricks.com/`) 2. Go to **Settings → App connections → Add connection** 3. Create an app. Note the **Client ID**. If confidential, generate a **Client Secret**. 4. Add **Redirect URIs** — e.g., `http://localhost:8080/callback` > **Three-way consistency rule:** The redirect URI must exactly match in three places: (1) the skill/code default, (2) the connector code, and (3) the **App connections → your app → Redirect URIs** in Databricks. A mismatch causes a silent OAuth callback failure with no clear error. The port `8080` is the default — if you use a different port, update all three locations. ### PKCE Flow Implementation **Authorize URL:** ``` {host}/oidc/v1/authorize ?response_type=code &client_id={client_id} &redirect_uri={redirect_uri} &scope=all-apis &code_challenge={S256_challenge} &code_challenge_method=S256 &state={state} ``` **Token URL:** ``` POST {host}/oidc/v1/token Content-Type: application/x-www-form-urlencoded grant_type=authorization_code &code={code_from_redirect} &redirect_uri={same_as_authorize} &client_id={client_id} &code_verifier={verifier} ``` ### Public vs Confidential OAuth Apps Databricks OAuth apps registered in App connections can be **public** (no client secret) or **confidential** (registered with a client secret). | App type | `client_secret` in token exchange | Error if missing | |----------|-----------------------------------|-----------------| | **Public** | Omit `client_secret` | n/a | | **Confidential** | **Required** — must include `client_secret` in POST body | `{"error": "invalid_client"}` | > ⚠️ **DIAGNOSTIC: `invalid_client` on token exchange** > Cause: The OAuth app is **confidential** (has a client secret registered) but `client_secret` was omitted from the token exchange POST body. > Fix: Add `client_secret=<secret>` to the POST body. The secret is shown once at app creation in Databricks App connections — if lost, regenerate it. > > Full token exchange body for confidential apps: > ``` > grant_type=authorization_code > &code=<code> > &redirect_uri=<uri> > &client_id=<id> > &code_verifier=<verifier> > &client_secret=<secret> ← required for confidential apps > ``` ### Python PKCE Implementation ```python import secrets import hashlib import base64 import webbrowser import threading from http.server import HTTPServer, BaseHTTPRequestHandler from urllib.parse import urlencode, urlparse, parse_qs import requests def generate_pkce(): """Generate PKCE code_verifier and code_challenge.""" verifier = secrets.token_urlsafe(64) digest = hashlib.sha256(verifier.encode()).digest() challenge = base64.urlsafe_b64encode(digest).rstrip(b"=").decode() return verifier, challenge class CallbackHandler(BaseHTTPRequestHandler): auth_code = None callback_received = threading.Event() def do_GET(self): if "/callback" in self.path: 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. Close this tab.") CallbackHandler.callback_received.set() else: self.send_response(204) self.end_headers() def log_message(self, format, *args): pass def run_pkce_flow(host, client_id, client_secret=None, redirect_uri="http://localhost:8080/callback", timeout=120): """Complete PKCE flow and return access token.""" # Parse port from redirect URI port = int(urlparse(redirect_uri).port or 8080) # Generate PKCE values verifier, challenge = generate_pkce() state = secrets.token_urlsafe(16) # Build authorize URL host = host.rstrip("/") if not host.startswith("https://"): host = "https://" + host auth_params = { "response_type": "code", "client_id": client_id, "redirect_uri": redirect_uri, "scope": "all-apis", "code_challenge": challenge, "code_challenge_method": "S256", "state": state, } auth_url = f"{host}/oidc/v1/authorize?{urlencode(auth_params)}" # Start callback server CallbackHandler.auth_code = None CallbackHandler.callback_received.clear() server = HTTPServer(("localhost", port), CallbackHandler) server.timeout = timeout # CRITICAL: prevents infinite blocking # Open browser webbrowser.open(auth_url) print(f"Opened browser for authentication...") # Wait for callback while not CallbackHandler.callback_received.is_set(): server.handle_request() server.server_close() # Use server_close(), NOT shutdown() if not CallbackHandler.auth_code: raise RuntimeError("No auth code received") # Exchange code for token token_data = { "grant_type": "authorization_code", "code": CallbackHandler.auth_code, "redirect_uri": redirect_uri, "client_id": client_id, "code_verifier": verifier, } if client_secret: token_data["client_secret"] = client_secret token_url = f"{host}/oidc/v1/token" resp = requests.post(token_url, data=token_data) resp.raise_for_status() return resp.json()["access_token"] ``` ### Java SDK with Custom OAuth App ```java import com.databricks.sdk.core.DatabricksConfig; import java.util.Arrays; import java.util.Map; public static String getTokenViaCustomOAuthApp( String host, String clientId, String clientSecret, String redirectUri) { String normalizedHost = host.startsWith("https://") ? host : "https://" + host; DatabricksConfig config = new DatabricksConfig() .setHost(normalizedHost) .setAuthType("external-browser") .setClientId(clientId) .setScopes(Arrays.asList("all-apis")); if (clientSecret != null && !clientSecret.isBlank()) { config.setClientSecret(clientSecret); } if (redirectUri != null && !redirectUri.isBlank()) { config.setOAuthRedirectUrl(redirectUri); } config.resolve(); Map<String, String> headers = config.authenticate(); return headers.get("Authorization").substring("Bearer ".length()); } ``` ### Go PKCE Implementation ```go package main import ( "crypto/rand" "crypto/sha256" "encoding/base64" "fmt" "net/http" "net/url" "strings" ) func generatePKCE() (verifier, challenge string) { b := make([]byte, 64) rand.Read(b) verifier = base64.RawURLEncoding.EncodeToString(b) h := sha256.Sum256([]byte(verifier)) challenge = base64.RawURLEncoding.EncodeToString(h[:]) return } func runPKCEFlow(host, clientID, clientSecret, redirectURI string, timeout int) (string, error) { verifier, challenge := generatePKCE() state := generateRandomString(16) // Parse redirect URI for port u, _ := url.Parse(redirectURI) port := u.Port() if port == "" { port = "8080" } // Build authorize URL if !strings.HasPrefix(host, "https://") { host = "https://" + host } authParams := url.Values{ "response_type": {"code"}, "client_id": {clientID}, "redirect_uri": {redirectURI}, "scope": {"all-apis"},
GitHub에서 보기
이 SKILL.md는 매우 커서 SkillsMP가 여기에는 첫 섹션만 미리 보여줍니다. GitHub에서 보기