# Execution context

AppKit manages Databricks authentication via two contexts:

* **ServiceContext** (singleton): Initialized at app startup with service principal credentials
* **ExecutionContext**: Determined at runtime - either service principal or user context

## Headers for user context[​](#headers-for-user-context "Direct link to Headers for user context")

* `x-forwarded-user`: required in production; identifies the user
* `x-forwarded-access-token`: required for user token passthrough

## Using `asUser(req)` for user-scoped operations[​](#using-asuserreq-for-user-scoped-operations "Direct link to using-asuserreq-for-user-scoped-operations")

The `asUser(req)` pattern allows plugins to execute operations using the requesting user's credentials:

```ts
// In a custom plugin route handler
router.post("/users/me/data", async (req, res) => {
  // Execute as the user (uses their Databricks permissions)
  const result = await this.asUser(req).query("SELECT ...");
  res.json(result);
});

// Service principal execution (default)
router.post("/system/data", async (req, res) => {
  const result = await this.query("SELECT ...");
  res.json(result);
});

```

## Context helper functions[​](#context-helper-functions "Direct link to Context helper functions")

Exported from `@databricks/appkit`:

* `getCurrentUserId()`: Returns user ID in user context, service user ID otherwise
* `getWorkspaceClient()`: Returns the appropriate WorkspaceClient for current context
* `getWarehouseId()`: `Promise<string>` (from `DATABRICKS_WAREHOUSE_ID` or auto-selected in dev)
* `getWorkspaceId()`: `Promise<string>` (from `DATABRICKS_WORKSPACE_ID` or fetched)

## Telemetry span attributes[​](#telemetry-span-attributes "Direct link to Telemetry span attributes")

The `plugin.execute` span created by the execution interceptor chain includes these attributes:

| Attribute                    | Type                    | Description                                                                        |
| ---------------------------- | ----------------------- | ---------------------------------------------------------------------------------- |
| `execution.context`          | `"user"` \| `"service"` | Whether the operation runs as a user (OBO) or service principal                    |
| `caller.id`                  | `string`                | The user ID (OBO) or service principal ID                                          |
| `execution.obo_dev_fallback` | `boolean`               | Set to `true` when an OBO call falls back to service principal in development mode |

These attributes are automatically added when your plugin uses `execute()` or `executeStream()`. All built-in plugins use these methods for their OBO operations. Custom plugins should do the same to get automatic telemetry instrumentation.

## Lakebase per-user connections[​](#lakebase-per-user-connections "Direct link to Lakebase per-user connections")

The Lakebase plugin uses a different mechanism for `asUser(req)`: instead of swapping the `WorkspaceClient` via AsyncLocalStorage, it creates a **separate `pg.Pool` per user**, each with its own OAuth token refresh. This is necessary because PostgreSQL connections are authenticated at connection time — the pool itself is the authentication boundary.

See [Lakebase plugin — per-user connections](./docs/plugins/lakebase.md#on-behalf-of-obo--per-user-connections) for details.

## Development mode behavior[​](#development-mode-behavior "Direct link to Development mode behavior")

In local development (`NODE_ENV=development`), if `asUser(req)` is called without a user token, it logs a warning and skips user impersonation — the operation runs with the default credentials configured for the app instead. The telemetry span will show `execution.context: "service"` with `execution.obo_dev_fallback: true` to distinguish these from regular service principal calls.
