Skip to content

Commit 60d7306

Browse files
mintlify[bot]keydunovcursoragent
authored
docs: add Databricks OAuth authentication recipe (cube-js#10701)
* Add recipe for Databricks OAuth authentication Generated-By: mintlify-agent * Generalize OAuth recipe to support any data source (Databricks, Snowflake) Generated-By: mintlify-agent * docs: address review feedback on OAuth recipe * docs: add beta callout to OAuth recipe * docs: move OAuth recipe to Admin > Connect to Data * docs: add OAuth setup steps and screenshots * docs: shorten OAuth page title * docs: rename OAuth page title to "Set up per-user OAuth" Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: mintlify[bot] <109931778+mintlify[bot]@users.noreply.github.com> Co-authored-by: Artyom Keydunov <artyom.keydunov@gmail.com> Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent b647dd5 commit 60d7306

5 files changed

Lines changed: 271 additions & 1 deletion

File tree

Lines changed: 269 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,269 @@
1+
---
2+
title: Set up per-user OAuth
3+
description: Configure Cube to authenticate each user with their own OAuth token, falling back to a service account for liveness checks.
4+
---
5+
6+
<Note>
7+
8+
This feature is in beta. Reach out to your account manager to have it
9+
enabled for your Cube Cloud deployment.
10+
11+
</Note>
12+
13+
## Use case
14+
15+
You want each user's queries to run under their own database identity
16+
using OAuth tokens managed by Cube Cloud. When a user's token is
17+
unavailable or expired, Cube falls back to a service account so that
18+
connectivity checks and background operations still work.
19+
20+
This pattern applies to any data source that supports OAuth, including
21+
[Databricks][ref-databricks-jdbc] and [Snowflake][ref-snowflake]. The
22+
examples below use Databricks; switch the `userCredentials` key and
23+
driver options for any other OAuth-capable data source.
24+
25+
Because every user connects with different credentials, you also need
26+
per-user query orchestrator state. Without this, one user's cached
27+
connection could leak to another.
28+
29+
## Prerequisites
30+
31+
- A [Cube Cloud][ref-cube-cloud] deployment connected to an
32+
OAuth-capable data source
33+
- OAuth configured in your data source so that Cube Cloud can
34+
obtain per-user tokens (via the **User Credentials** feature)
35+
- A service account credential (token or password) stored as an
36+
environment variable for fallback connectivity
37+
38+
<Warning>
39+
40+
The service account credential is used only as a fallback for Cube's
41+
internal liveness checks and background operations. Grant it the minimum
42+
permissions necessary β€” ideally read-only access to the required schemas β€”
43+
to limit exposure if the credential is compromised.
44+
45+
</Warning>
46+
47+
## Set up the OAuth app
48+
49+
Before configuring Cube to use per-user OAuth, register your data
50+
source as an OAuth app in Cube Cloud:
51+
52+
<Steps>
53+
54+
<Step title="Open the OAuth apps settings">
55+
56+
In Cube Cloud, go to **Admin β†’ Integrations β†’ OAuth apps** and click
57+
**Add**.
58+
59+
<Frame>
60+
<img src="/images/admin/connect-to-data/oauth-add-app.png" alt="Admin Integrations page showing the OAuth apps section with the Add button" />
61+
</Frame>
62+
63+
</Step>
64+
65+
<Step title="Fill out the OAuth app details">
66+
67+
Provide the OAuth app metadata from your data source: **Name**,
68+
**Auth URL**, **Token URL**, **Client ID**, **Client Secret**, and any
69+
required **Scopes**. Copy the **Redirect URI** shown in this form and
70+
register it with your data source's OAuth provider, then click
71+
**Create**.
72+
73+
<Frame>
74+
<img src="/images/admin/connect-to-data/oauth-fill-fields.png" alt="New OAuth app form with fields for Name, Auth URL, Token URL, Client ID, Client Secret, Scopes, and Redirect URI" />
75+
</Frame>
76+
77+
</Step>
78+
79+
<Step title="Authorize the app">
80+
81+
Open the user menu and go to **Connected apps**. Find your OAuth app
82+
and click **Authorize** to generate an access token.
83+
84+
You'll need to repeat this step whenever the token expires.
85+
86+
<Frame>
87+
<img src="/images/admin/connect-to-data/oauth-authorize.png" alt="Connected apps page showing the OAuth integration with an Authorize action" />
88+
</Frame>
89+
90+
</Step>
91+
92+
</Steps>
93+
94+
## Configuration
95+
96+
The configuration uses two options from the
97+
[configuration file reference][ref-config]:
98+
99+
- [`driver_factory`][ref-driver-factory] β€” dynamically selects the
100+
authentication credential per request
101+
- [`context_to_orchestrator_id`][ref-context-to-orchestrator-id] β€” gives
102+
each user their own query orchestrator instance (database connections,
103+
execution queues, pre-aggregation table caches)
104+
105+
### Environment variables
106+
107+
Set the environment variables for your data source. The examples below
108+
show Databricks and Snowflake; adapt them to your specific setup.
109+
110+
<Tabs>
111+
112+
<Tab title="Databricks">
113+
114+
```dotenv
115+
CUBEJS_DB_TYPE=databricks-jdbc
116+
CUBEJS_DB_DATABRICKS_URL=jdbc:databricks://dbc-XXXXXXX-XXXX.cloud.databricks.com:443/default;transportMode=http;ssl=1;httpPath=sql/protocolv1/o/XXXXX/XXXXX;AuthMech=3;UID=token
117+
CUBEJS_DB_DATABRICKS_TOKEN=dapi_service_account_token
118+
CUBEJS_DB_DATABRICKS_ACCEPT_POLICY=true
119+
# Optional: specify a catalog
120+
CUBEJS_DB_DATABRICKS_CATALOG=my_catalog
121+
```
122+
123+
</Tab>
124+
125+
<Tab title="Snowflake">
126+
127+
```dotenv
128+
CUBEJS_DB_TYPE=snowflake
129+
CUBEJS_DB_SNOWFLAKE_ACCOUNT=XXXXXXXXX.us-east-1
130+
CUBEJS_DB_SNOWFLAKE_WAREHOUSE=MY_SNOWFLAKE_WAREHOUSE
131+
CUBEJS_DB_NAME=my_snowflake_database
132+
CUBEJS_DB_USER=service_account_user
133+
CUBEJS_DB_PASS=service_account_password
134+
CUBEJS_DB_SNOWFLAKE_ROLE=MY_ROLE
135+
```
136+
137+
</Tab>
138+
139+
</Tabs>
140+
141+
### Configuration file
142+
143+
The examples below use Databricks. To target a different data source,
144+
swap `userCredentials.databricks` for the matching key (for example,
145+
`userCredentials.snowflake`) and update the `driver_factory` return
146+
value with the correct `type` and driver-specific options. See the
147+
[data sources reference][ref-data-sources] for available drivers.
148+
149+
<Tabs>
150+
151+
<Tab title="Python">
152+
153+
```python cube.py
154+
from cube import config
155+
import os
156+
157+
158+
@config("driver_factory")
159+
def driver_factory(ctx: dict) -> dict:
160+
# Extract the Cube Cloud security context, which contains
161+
# per-user OAuth credentials when available.
162+
# For other data sources, swap "databricks" for "snowflake", etc.
163+
databricks_creds = (
164+
ctx
165+
.get("securityContext", {})
166+
.get("cubeCloud", {})
167+
.get("userCredentials", {})
168+
.get("databricks", {})
169+
)
170+
171+
# Only use the OAuth token when the credential status is "active".
172+
# An expired or revoked token falls back to the service account.
173+
oauth_token = (
174+
databricks_creds.get("accessToken")
175+
if databricks_creds.get("status") == "active"
176+
else None
177+
)
178+
179+
return {
180+
"type": "databricks-jdbc",
181+
"url": os.environ["CUBEJS_DB_DATABRICKS_URL"],
182+
# Prefer the user's OAuth token; fall back to the service account token
183+
"token": oauth_token or os.environ["CUBEJS_DB_DATABRICKS_TOKEN"],
184+
"acceptPolicy": True,
185+
"catalog": os.environ.get("CUBEJS_DB_DATABRICKS_CATALOG"),
186+
}
187+
188+
189+
@config("context_to_orchestrator_id")
190+
def context_to_orchestrator_id(ctx: dict) -> str:
191+
# Give each user a separate orchestrator instance (DB connections,
192+
# execution queues, pre-aggregation caches)
193+
username = (
194+
ctx
195+
.get("securityContext", {})
196+
.get("cubeCloud", {})
197+
.get("username", "default")
198+
)
199+
return f"CUBE_APP_{username}"
200+
```
201+
202+
</Tab>
203+
204+
<Tab title="JavaScript">
205+
206+
```javascript cube.js
207+
module.exports = {
208+
driverFactory: ({ securityContext }) => {
209+
// Extract the Cube Cloud security context, which contains
210+
// per-user OAuth credentials when available.
211+
// For other data sources, swap `databricks` for `snowflake`, etc.
212+
const databricksCreds =
213+
securityContext?.cubeCloud?.userCredentials?.databricks ?? {};
214+
215+
// Only use the OAuth token when the credential status is "active".
216+
// An expired or revoked token falls back to the service account.
217+
const oauthToken =
218+
databricksCreds.status === "active"
219+
? databricksCreds.accessToken
220+
: null;
221+
222+
return {
223+
type: "databricks-jdbc",
224+
url: process.env.CUBEJS_DB_DATABRICKS_URL,
225+
// Prefer the user's OAuth token; fall back to the service account token
226+
token: oauthToken || process.env.CUBEJS_DB_DATABRICKS_TOKEN,
227+
acceptPolicy: true,
228+
catalog: process.env.CUBEJS_DB_DATABRICKS_CATALOG,
229+
};
230+
},
231+
232+
// Give each user a separate orchestrator instance (DB connections,
233+
// execution queues, pre-aggregation caches)
234+
contextToOrchestratorId: ({ securityContext }) => {
235+
const username = securityContext?.cubeCloud?.username ?? "default";
236+
return `CUBE_APP_${username}`;
237+
},
238+
};
239+
```
240+
241+
</Tab>
242+
243+
</Tabs>
244+
245+
## How it works
246+
247+
1. **User makes a request** β€” Cube Cloud attaches the user's OAuth
248+
credentials to `securityContext.cubeCloud.userCredentials.<data_source>`
249+
(for example, `.databricks` or `.snowflake`).
250+
251+
2. **`driver_factory` resolves the credential** β€” If the credential status
252+
is `active`, the user's OAuth token is used. Otherwise, Cube falls back
253+
to the service account credential stored in environment variables.
254+
255+
3. **Per-user orchestrator** β€”
256+
[`context_to_orchestrator_id`][ref-context-to-orchestrator-id] returns
257+
a unique key per username, so each user gets their own database
258+
connection pool, execution queues, and pre-aggregation table cache.
259+
Without this, Cube would share a single cached connection across all
260+
users, causing one user's credentials to be reused for another user's
261+
queries.
262+
263+
[ref-config]: /reference/configuration/config
264+
[ref-driver-factory]: /reference/configuration/config#driver_factory
265+
[ref-context-to-orchestrator-id]: /reference/configuration/config#context_to_orchestrator_id
266+
[ref-databricks-jdbc]: /admin/connect-to-data/data-sources/databricks-jdbc
267+
[ref-snowflake]: /admin/connect-to-data/data-sources/snowflake
268+
[ref-data-sources]: /admin/connect-to-data/data-sources
269+
[ref-cube-cloud]: /docs/introduction

β€Ždocs-mintlify/docs.jsonβ€Ž

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,8 @@
262262
]
263263
},
264264
"admin/connect-to-data/multiple-data-sources",
265-
"admin/connect-to-data/concurrency"
265+
"admin/connect-to-data/concurrency",
266+
"admin/connect-to-data/oauth-authentication"
266267
]
267268
},
268269
{
318 KB
Loading
295 KB
Loading
322 KB
Loading

0 commit comments

Comments
Β (0)