Every Databricks App can act as its own service principal (app authorization) or as the signed-in user (on-behalf-of-user authorization) — the choice hinges on whether the data is shared or per-user.
A Databricks App authorizes either as its own service principal (app authorization) or on behalf of the signed-in user via a forwarded user token (user authorization / OBO).
It is the security boundary of every app. App authorization gives one shared identity (good for service/shared data); user authorization makes the app inherit each viewer's Unity Catalog permissions — including row filters and column masks — so users only see what they're entitled to. Picking the wrong model either over-shares data or breaks per-user access.
Each app gets a dedicated SP as its identity; Databricks injects DATABRICKS_CLIENT_ID / DATABRICKS_CLIENT_SECRET, which the SDK uses automatically.
Warehouses, UC tables, serving endpoints, secrets, Genie are declared as app resources and granted to the SP (e.g. CAN USE, SELECT, CAN QUERY).
The app reads the user's token from the X-Forwarded-Access-Token header and calls Databricks as that user — enforcing their existing UC permissions.
OBO is gated by configured scopes (e.g. sql, dashboards.genie, files.files; defaults iam.access-control:read, iam.current-user:read) that cap what the app may do as the user.
Every request carries X-Forwarded-User, X-Forwarded-Email, X-Forwarded-Preferred-Username (from the IdP) plus the OBO X-Forwarded-Access-Token.
End users sign in to the app automatically through Databricks OAuth/SSO; no separate login — the platform handles the identity provider flow.
DATABRICKS_CLIENT_ID/SECRET); every user sees the same SP-scoped data.Python · databricks-sdkfrom databricks.sdk import WorkspaceClient
from flask import request
# Default client = the app's SERVICE PRINCIPAL (app authorization).
# Uses injected DATABRICKS_CLIENT_ID / DATABRICKS_CLIENT_SECRET automatically.
app_sp = WorkspaceClient()
# OBO: act AS the signed-in user via the forwarded user token.
user_token = request.headers.get("X-Forwarded-Access-Token") # the line that matters
as_user = WorkspaceClient(host=app_sp.config.host, token=user_token)
as_user.tables.get("main.sales.customers") # enforces the USER's UC grants
sql) isn't added, the user-token call fails.X-Forwarded-Access-Token is short-lived — don't cache it.DATABRICKS_CLIENT_ID/SECRET."X-Forwarded-Access-Token and calls as the user — so it's a double-grant: the SP and the user both need access, and the app is capped by configured scopes."Verified against docs.databricks.com · June 2026