A Streamlit application deployed as a Databricks App that demonstrates On-Behalf-Of (OBO) user authentication. The app executes all API calls using the logged-in user's identity, ensuring Unity Catalog permissions (row/column filters, ACLs) are enforced per user.
- Displays the authenticated user's identity (username, display name, ID, groups)
- Shows the workspace host and OBO token scopes for debugging
- Queries any Unity Catalog table via the SQL Statement API using the user's OBO token
- Deployable as a Databricks Asset Bundle (DAB) with environment-specific targets
obo_test/
├── databricks.yml # DAB configuration (bundle name, targets)
├── resources/
│ └── obo_test.app.yml # App resource definition (name, scopes, permissions)
├── src/
│ └── app/
│ ├── app.py # Streamlit application
│ ├── app.yaml # App runtime config (command, env vars)
│ └── requirements.txt # Python dependencies
├── .gitignore
└── README.md
┌─────────────────────────────────────────────────────────────┐
│ User's Browser │
│ │
│ 1. User navigates to the Databricks App URL │
│ 2. Databricks OAuth flow authenticates the user │
│ 3. Databricks proxy injects x-forwarded-access-token │
│ header with the user's OBO token │
└──────────────────────┬──────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Databricks Apps Runtime │
│ (Python 3.11, 2 vCPU, 6 GB RAM) │
│ │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ Streamlit App (app.py) │ │
│ │ │ │
│ │ ┌─────────────────┐ ┌──────────────────────────┐ │ │
│ │ │ OBO Token │ │ Environment Variables │ │ │
│ │ │ Extraction │ │ │ │ │
│ │ │ │ │ DATABRICKS_HOST │ │ │
│ │ │ x-forwarded- │ │ DATABRICKS_WAREHOUSE_ID │ │ │
│ │ │ access-token │ │ DATABRICKS_CLIENT_ID * │ │ │
│ │ │ header │ │ DATABRICKS_CLIENT_SECRET*│ │ │
│ │ └────────┬─────────┘ └──────────────────────────┘ │ │
│ │ │ │ │
│ │ │ Bearer token │ │
│ │ ▼ │ │
│ │ ┌─────────────────────────────────────────────────┐ │ │
│ │ │ REST API Calls (requests) │ │ │
│ │ │ │ │ │
│ │ │ GET /api/2.0/preview/scim/v2/Me │ │ │
│ │ │ → User identity (name, groups, ID) │ │ │
│ │ │ │ │ │
│ │ │ POST /api/2.0/sql/statements │ │ │
│ │ │ → Execute SQL as the logged-in user │ │ │
│ │ │ │ │ │
│ │ │ GET /api/2.0/sql/statements/{id} │ │ │
│ │ │ → Poll for query completion │ │ │
│ │ └─────────────────────────────────────────────────┘ │ │
│ └───────────────────────────────────────────────────────┘ │
│ │
│ * SP credentials are auto-injected but NOT used by this │
│ app. All calls use the OBO user token exclusively. │
└──────────────────────┬──────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Databricks Workspace │
│ │
│ ┌──────────────┐ ┌──────────────────────────────────┐ │
│ │ SCIM API │ │ SQL Statement API │ │
│ │ │ │ │ │
│ │ /Me │ │ Executes queries on a SQL │ │
│ │ endpoint │ │ Warehouse using the user's │ │
│ │ │ │ identity and permissions │ │
│ └──────────────┘ └───────────────┬──────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────┐ │
│ │ SQL Warehouse │ │
│ │ │ │
│ │ Unity Catalog enforces: │ │
│ │ - Table ACLs │ │
│ │ - Row/column filters │ │
│ │ - Data masking │ │
│ └──────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
- User visits the app URL — Databricks triggers an OAuth consent flow
- Databricks proxy authenticates the user and injects the
x-forwarded-access-tokenHTTP header containing the user's OBO JWT - Streamlit app reads the token from
st.context.headersand uses it as a Bearer token for all REST API calls - No SDK used — the app deliberately avoids the Databricks SDK (
WorkspaceClient,Config) because the auto-injected service principal environment variables (DATABRICKS_CLIENT_ID,DATABRICKS_CLIENT_SECRET) conflict with the OBO token, causing auth validation errors. All API calls use plainrequestswith the OBO Bearer token.
The Databricks Apps runtime injects service principal OAuth credentials (DATABRICKS_CLIENT_ID/DATABRICKS_CLIENT_SECRET) as environment variables. Both the Databricks SDK (WorkspaceClient) and the SQL connector (databricks-sql-connector) auto-detect these and refuse to accept an additional access_token or token parameter, raising:
ValueError: more than one authorization method configured: oauth and pat
Using requests directly with the OBO Bearer token bypasses this entirely.
| Library | Version | Purpose |
|---|---|---|
| streamlit | 1.38.0 (pre-installed) | Web UI framework |
| requests | (pre-installed) | HTTP client for Databricks REST APIs |
| databricks-sql-connector | (in requirements.txt) | Not actively used; kept as a dependency |
Standard library modules used: os, time, base64, json
Configured in the DAB resource file (resources/obo_test.app.yml):
| Scope | Purpose |
|---|---|
sql |
Execute SQL queries via the Statement API |
iam.current-user:read |
Read current user identity (included by default) |
iam.access-control:read |
Read access control info (included by default) |
- Workspace admin must enable user authorization (Public Preview feature)
- A SQL Warehouse accessible to app users
- Databricks CLI installed and configured with a profile
# Validate the bundle
databricks bundle validate -t dev --profile <your-profile>
# Deploy
databricks bundle deploy -t dev --profile <your-profile>
# Start the app
databricks bundle run obo_test -t dev --profile <your-profile>- Add a SQL Warehouse resource to the app via the Databricks UI (referenced by
valueFrom: sql-warehouseinapp.yaml) - Verify user authorization is enabled and the
sqlscope appears in the app settings - Open the app URL in an incognito window to ensure a fresh OAuth token with all scopes
| Target | Mode | Description |
|---|---|---|
dev |
development | Default target for development |
Add additional targets (staging, prod) in databricks.yml as needed.
| Variable | Source | Description |
|---|---|---|
DATABRICKS_HOST |
Auto-injected | Workspace URL |
DATABRICKS_WAREHOUSE_ID |
valueFrom: sql-warehouse |
Default SQL Warehouse ID |
DATABRICKS_CLIENT_ID |
Auto-injected | SP client ID (not used by this app) |
DATABRICKS_CLIENT_SECRET |
Auto-injected | SP client secret (not used by this app) |