Skip to main content

Authentication & Permissions

This is the authoritative reference for how identity and authorization work in Genie Workbench. The app uses a dual-identity model: the signed-in user's token for interactive operations and the app's Service Principal (SP) for background jobs and API fallback.

Overview​

OBO (On-Behalf-Of) Authentication​

How it works​

  1. The Databricks Apps platform authenticates the user via SSO and forwards their OAuth access token in the x-forwarded-access-token HTTP header on every request.

  2. OBOAuthMiddleware in backend/main.py intercepts all /api/* requests and calls set_obo_user_token(token) from backend/services/auth.py.

  3. set_obo_user_token() creates a WorkspaceClient configured with auth_type="pat" using the user's token. This is stored in a Python ContextVar so it is scoped to the current request.

    note

    The explicit auth_type="pat" is required because the Databricks Apps environment has DATABRICKS_CLIENT_ID / DATABRICKS_CLIENT_SECRET set for the SP. Without it, the SDK would default to OAuth M2M instead of the user's token.

  4. Most downstream service calls use get_workspace_client(), which returns the OBO client if set, otherwise falls back to the SP singleton.

  5. Routes whose visibility must stay bound to the signed-in user instead call require_obo_workspace_client(), which returns only the request's user client and never falls back to the SP. If no OBO client is set, the request fails rather than widening access. GenieWatch traffic-gap reads use this path — see OBO-only routes.

SSE streaming caveat​

For the Create Agent's Server-Sent Events endpoint, the ContextVar is not cleared after call_next in the middleware. This is because the response body streams lazily — the generator runs after the middleware returns. Streaming handlers stash the raw token on request.state.user_token and re-set it inside the generator function.

OBO-only routes​

Some reads expose data that the app's SP could see but the signed-in user must not. Those routes require user authorization and have no SP fallback.

RouteRequirementReason
GET /api/watch/spaces/{space_id}/traffic-gapsOBO only, user needs CAN_MANAGE on the AgentReads production conversation traffic. The Genie list conversations API requires CAN_MANAGE, so SP fallback would let a non-manager read other users' questions.

What OBO protects​

Users can only interact with resources they have permission to access:

  • Unity Catalog: catalogs, schemas, tables visible to the user
  • Genie Agents: only agents the user can manage
  • SQL Warehouse: queries execute under the user's identity
  • Genie Agent creation/modification: agents are created and patched as the user

Service Principal (SP)​

Singleton client​

get_service_principal_client() in backend/services/auth.py always returns the SP singleton — a WorkspaceClient initialized from the platform environment variables (DATABRICKS_CLIENT_ID, DATABRICKS_CLIENT_SECRET, DATABRICKS_HOST). On Databricks Apps, this is the app's own SP.

When SP is used​

The SP is used in three scenarios:

1. Genie API scope fallback​

Some user OAuth tokens lack the dashboards.genie scope, even though it is listed in app.yaml user_api_scopes. When get_genie_space() or list_genie_spaces() in backend/services/genie_client.py catches a scope error, it transparently retries with the SP:

def _is_scope_error(e: Exception) -> bool:
msg = str(e).lower()
return "scope" in msg or "insufficient_scope" in msg

For this fallback to work, the SP must have CAN_MANAGE on each Genie Agent.

2. Optimization job execution​

The Auto-Optimize pipeline runs as a Lakeflow Job — a long-running, multi-task DAG. Lakeflow Jobs execute in a separate environment with a fixed run_as identity. There is no mechanism to forward the user's short-lived OAuth token into a background job that may run for minutes.

The job is configured to run_as the app's SP. At startup, _ensure_gso_job_run_as() in backend/main.py verifies and updates the job's run_as to match the current app SP.

3. GSO Delta table operations​

Reads and writes to the optimizer state tables under GSO_CATALOG.GSO_SCHEMA use the SP because these tables are owned by the SP and are not user-scoped. See Durable state for the table list.

Optimization Trigger Flow​

When a user triggers Auto-Optimize, the app uses both identities in a carefully sequenced flow:

Source: packages/genie-space-optimizer/src/genie_space_optimizer/integration/trigger.py — trigger_optimization()

Requested OAuth Scopes​

The user_api_scopes in app.yaml request these scopes for the user's OBO token:

ScopePurpose
sqlExecute SQL queries via warehouses
dashboards.genieAccess Genie Agent API
serving.serving-endpointsCall model serving endpoints (LLM)
catalog.catalogs:readBrowse Unity Catalog catalogs
catalog.schemas:readBrowse Unity Catalog schemas
catalog.tables:readBrowse Unity Catalog tables
files.filesAccess workspace files

If the workspace or user's OAuth consent doesn't grant all scopes, the app degrades gracefully — the SP fallback handles the most common gap (dashboards.genie). OBO-only routes are the exception: they return 401 instead of falling back.

SP Permissions Required​

Per Genie Agent​

PermissionPurpose
CAN_MANAGEAPI fallback when user token lacks Genie scope; applying optimization patches during the GSO pipeline

Grant via the Genie Agent sharing UI, or let the installer do it — both notebooks/install.py and scripts/install.sh grant the SP access to your visible Genie Agents.

Per referenced data schema​

PermissionPurpose
USE_CATALOGAccess the catalog containing the schema
USE_SCHEMAAccess the schema
SELECTRead table data during optimization benchmarks
GRANT USE_CATALOG ON CATALOG <catalog> TO `<service-principal-name>`;
GRANT USE_SCHEMA ON SCHEMA <catalog>.<schema> TO `<service-principal-name>`;
GRANT SELECT ON SCHEMA <catalog>.<schema> TO `<service-principal-name>`;

GSO optimizer schema​

The SP needs full access to the optimizer state schema (<GSO_CATALOG>.genie_space_optimizer):

PermissionPurpose
USE_CATALOGAccess the catalog
USE_SCHEMAAccess the schema
SELECTRead optimizer state tables
MODIFYWrite optimizer state tables
CREATE_TABLECreate state tables on first run
CREATE_FUNCTIONCreate UDFs if needed
CREATE_MODELGranted for historical/forward compatibility; the pipeline no longer registers models
CREATE_VOLUMEArtifact storage
EXECUTEExecute functions
MANAGESchema management

These are granted automatically by scripts/grant_permissions.py during deployment.

Complete Permission Boundary​

OperationIdentityCode ReferenceRationale
Browse Genie Agents, UC catalogs/schemas/tablesOBO (user)services/uc_client.py, routers/create.pyUser sees only what they have access to
GenieWatch traffic-gap analysisOBO only, no SP fallbackwatch/routers/traffic_gaps.py require_obo_workspace_client()Conversation traffic requires CAN_MANAGE; SP fallback would leak other users' questions
Genie API — fetch/list agentsOBO → SP fallbackservices/genie_client.py _is_scope_error()User token may lack dashboards.genie scope
Create Agent — tools, SQL, agent creationOBO (user)services/create_agent.py, services/create_agent_tools.pyAgent created under user identity
Trigger optimization — permission checkOBO (user)integration/trigger.py user_can_edit_space()Verify user has CAN_EDIT/CAN_MANAGE
Trigger optimization — SP entitlement checkSPintegration/trigger.py sp_can_manage_space()Verify SP can manage the agent
Optimization job submissionSPbackend/job_launcher.py submit_optimization()jobs.run_now() requires SP
Optimization job execution (4-task DAG)SP (run_as)backend/job_launcher.py ensure_job_run_as()Lakeflow Jobs have no OBO mechanism
GSO Delta table reads/writesSProuters/auto_optimize.py _delta_query()Optimizer state tables owned by SP
Lakebase persistenceSPservices/lakebase.pyApp-level storage, not user-scoped
IQ ScanOBO (user) → SP for GSO dataservices/scanner.pySpace fetch via OBO; GSO run data via SP
Apply optimization resultsOBO (user)routers/auto_optimize.py /runs/{id}/applyChanges applied under user identity

Security Considerations​

  1. Authorization before execution — the user must have CAN_EDIT or CAN_MANAGE on the Genie Agent before any optimization job is submitted. This check happens with the user's OBO token, not the SP.

  2. SP entitlement validated — even if the user is authorized, the SP must also have CAN_MANAGE on the agent. If the SP lacks access, the trigger is rejected with a clear error.

  3. Minimum-privilege SP — the SP only needs read access to referenced data schemas (for benchmarking) and manage access to the GSO state schema. It does not need workspace-admin privileges.

  4. No token forwarding to jobs — the design intentionally does not pass user tokens into background jobs. Short-lived OAuth tokens would expire during long-running DAGs, and storing user credentials in job parameters would be a security risk.

  5. Audit trail — the triggering user's email is recorded in the optimization run metadata, providing traceability even though the job executes as the SP.