Inject OBO access token in apps run-local#5795
Conversation
The deployed Apps platform fronts each slot with an OAuth2 proxy that injects the caller's on-behalf-of (OBO) token as X-Forwarded-Access-Token. `databricks apps run-local` starts a local proxy that injects the identity headers (X-Forwarded-Email, etc.) but not the token, so OBO code paths can't be exercised locally — the app sees the header as missing and every OBO route fails. Mint a token from the CLI's own credentials (the same profile already used for CurrentUser.Me) and inject it as X-Forwarded-Access-Token. The value is resolved per request via a new appproxy InjectHeaderFunc so a browser session outliving a single token still gets a refreshed one; the SDK token source returns a cached token without blocking in the steady state. A resolution failure fails the request with 502 rather than forwarding it without the header. Co-authored-by: Isaac
f7263cb to
1c6609e
Compare
|
An authorized user can trigger integration tests manually by following the instructions below: Trigger: Inputs:
Checks will be approved automatically on success. |
Approval status: pending
|
theof-db
left a comment
There was a problem hiding this comment.
Were you able to test this e2e locally?
yes! I was able to run it against my app locally and see the token forwarded to my app. I wrote a note about this in the how it works section |
What did you change, and why?
databricks apps run-localstarts a local proxy that fronts the app andinjects the identity headers the deployed Apps OAuth2 proxy would
(
X-Forwarded-Email,X-Forwarded-User, ...) — but notX-Forwarded-Access-Token. That token is the on-behalf-of (OBO) credentialthe deployed platform forwards so an app can call Databricks APIs as the
calling user. Locally it is simply absent, so every OBO code path sees the
header as missing and fails, and OBO-gated surfaces can't be exercised in a
browser without deploying.
This injects
X-Forwarded-Access-Tokentoo, minted from the CLI's owncredentials (the same profile already used for
CurrentUser.Me), matchingthe deployed proxy.
libs/appproxy: addInjectHeaderFunc(key, fn)for a header whose value isresolved per request. Used for the token because it expires: the SDK token
source returns a cached token without blocking in the steady state and
refreshes on demand, so a browser session that outlives a single token keeps
working. A resolution error fails the request with 502 rather than
forwarding it without the header. Both the HTTP and WebSocket paths now go
through a shared
applyInjectedHeaders.cmd/apps/run_local.go: inject the token fromw.Config.GetTokenSource(),mirroring the existing idiom in
cmd/apps/logs.go.libs/apps/runlocal: export the header name as a constant.How do you know it works?
Unit tests cover the new
InjectHeaderFunc(per-request resolution + the502-on-error path). Manually verified end-to-end against a Lakehouse App: a
request straight to the app returns 401 (OBO fails, as before), while the same
request through the run-local proxy returns 200 with OBO-backed pages
rendering as the calling user.