Semantic Layer Authentication & Authorization: Ontology, Cube, Trino, and Ranger
This page covers how a user's identity flows from the BFF API through the Ontology backend and Cube semantic layer to Trino query execution, and where Ranger enforces access policies.
For a higher-level overview of the token exchange model, see Token Exchange and Data Access.
Routes Covered
All routes are defined in src/routes/ontologyRoute.js and protected by validateJWT + exchangeTokenForBackendMiddleware.
| Method | Route | Purpose |
|---|---|---|
GET | /ontology/v1/objects | List ontology objects |
GET | /ontology/v1/objects/catalog | List catalog-level objects |
POST | /ontology/v1/objects | Create ontology object |
GET | /ontology/v1/objects/by-name/:name | Get object by name |
GET | /ontology/v1/objects/:object_id | Get object by ID |
DELETE | /ontology/v1/objects/:object_id | Delete object |
GET | /ontology/v1/cube/repository | Get Cube data model repository |
GET | /ontology/v1/cube/schema-version | Get Cube schema version |
POST | /ontology/v1/cube/load | Execute a Cube query (analytical load) |
GET | /ontology/v1/cube/load | Execute a Cube query via query params |
POST | /ontology/v1/cube/sql | Execute Cube SQL endpoint |
POST | /ontology/v1/cube/dry-run | Dry-run a Cube query |
GET | /ontology/v1/cube/dry-run | Dry-run via query params |
GET | /ontology/v1/cube/meta | Get Cube metadata (measures, dimensions) |
GET/POST/PUT/DELETE | /ontology/v1/workbooks/* | Workbook CRUD |
Cube routes (/ontology/v1/cube/*) are the ones that result in SQL execution against Trino.
End-to-End Flow
Step 1: JWT Validation — BFF
The validateJWT middleware validates the user's Keycloak JWT. The exchange to the Ontology backend audience happens next via exchangeTokenForBackendMiddleware.
Step 2: Token Exchange — BFF to Ontology Backend
The BFF exchanges the user JWT for a token scoped to the Ontology backend before forwarding:
- Audience:
${extWorkspaceId}-${config.ontology.audience}(default:{workspaceId}-ontology-backend) - The exchanged token is stored on
req.exchangedToken - The user's original session token is never forwarded to the Ontology backend
- Exchanged tokens are cached in Valkey (default TTL: 180s)
src/services/ontologyService.js (genericOntologyRequest) forwards all proxied requests with:
Authorization: Bearer ${req.exchangedToken}
ONTOLOGY_AUTH_DISABLED=true bypasses token exchange entirely and forwards the raw user session token to the Ontology backend. There is no guard preventing this from being set in production. See Security Gaps.
Step 3: Ontology Backend to Cube
The Ontology backend receives the BFF request carrying the user's ontology-scoped JWT, extracts the username from its claims, and creates a Cube-specific HS256 JWT signed with CUBE_API_SECRET. This JWT contains a trino_user claim set to the authenticated user's identity.
This is the handoff point between the Keycloak-based auth chain and Cube's own auth scheme.
TODO: Confirm how the Ontology backend derives the value it sets in
trino_user— whether it usespreferred_username,sub, or another claim from the incoming ontology-scoped JWT. This determines which identity Ranger sees.
The source claim used for trino_user has not been confirmed. If it does not match the username format Ranger expects, all semantic layer queries will be denied. See Security Gaps.
Step 4: Cube Auth — Validating the Incoming JWT
Cube validates every incoming request against a symmetric HS256 secret (CUBE_API_SECRET). The token must contain a trino_user claim identifying the originating user. Requests without a valid trino_user are rejected.
Health probe requests (/livez, /readyz) bypass this check and run under the Trino service account identity.
Cube uses a symmetric HS256 secret rather than Keycloak RS256 JWTs. This is a separate trust boundary from the rest of the platform's token exchange chain — the Ontology backend is the issuer.
Step 5: driverFactory — Per-User Trino Identity
driverFactory is Cube's equivalent of Superset's DATABASE_CONNECTION_MUTATOR. For each query it builds a Trino connection using the trino_user from the validated JWT as the query identity, while using a shared service account (TRINO_SERVICE_USER) for the underlying BasicAuth connection credentials.
The split mirrors the Superset pattern:
| Value | Purpose | |
|---|---|---|
| Query user | trino_user from JWT claim | Identity Ranger evaluates policies against |
| Connection credentials | TRINO_SERVICE_USER / TRINO_SERVICE_PASSWORD | Credentials Trino uses to accept the connection |
Each unique trino_user also gets its own Cube query orchestrator, ensuring query isolation between users.
Step 6: Ranger Policy Enforcement — Trino
Because driverFactory sets user to the real user's identity (not the service account), Ranger evaluates per-user policies on every semantic layer query:
| What Ranger checks | How it's resolved |
|---|---|
| User identity | trinoUser from the Cube JWT claim (via user field → X-Trino-User) |
| Resource | Catalog → Schema → Table → Column |
| Access type | select, row filters, column masking |
| Policy match | First matching policy wins |
Row filters and column masks configured in Ranger apply transparently — the user never sees the restricted data and Cube receives only the filtered result set.
The TRINO_SERVICE_USER credentials handle connection authentication. Ranger policy enforcement uses the user field, not the BasicAuth user. The service account must have impersonation rights in Trino to submit queries on behalf of other users.
TODO: Confirm that
TRINO_SERVICE_USERis granted theimpersonateprivilege in the Rangertrinouserseed policy. If it does not have this privilege, Trino may reject queries where the BasicAuth user and the queryuserdiffer.
It has not been confirmed that the Cube service account holds the impersonate privilege in Ranger. Without it, all non-probe Cube queries to Trino will be rejected. See Security Gaps.
Cube Model Loading
Cube loads its semantic data model dynamically from the Ontology backend. For this internal call, Cube first uses an ontology_access_token embedded in the request's security context (if provided by the Ontology backend). If none is present, it falls back to a Keycloak client credentials token issued to the Cube service account.
Schema versioning follows the same token resolution path and is used to invalidate the Cube model cache when the data model changes.
Auth Bypass (BFF Layer)
Setting ONTOLOGY_AUTH_DISABLED=true causes the BFF to skip token exchange and forward the user's raw JWT to the Ontology backend. This is a development convenience and must not be enabled in production.
See Security Gaps for the risk this poses if misconfigured.
Configuration
Key configuration areas:
- BFF: Ontology backend URL and Keycloak audience for the token exchange, plus the dev auth bypass flag
- Cube:
CUBEJS_API_SECRET(HS256 signing secret), Trino connection details,TRINO_SERVICE_USER/PASSWORDfor the connection credentials, and Keycloak client credentials for model loading fallback
Go Deeper
- Token Exchange and Data Access — token exchange model
- SQL Auth — Superset, Trino & Ranger — Ranger enforcement detail
- Security Gaps — known gaps: Cube identity propagation unknown, auth bypass risk