Documentation
Security context

Security context

Your authentication server issues JWTs to your client application, which, when sent as part of the request, are verified and decoded by Cube to get security context claims to evaluate access control rules. Inbound JWTs are decoded and verified using industry-standard JSON Web Key Sets (JWKS) (opens in a new tab).

For access control or authorization, Cube allows you to define granular access control rules for every cube in your data model. Cube uses both the request and security context claims in the JWT token to generate a SQL query, which includes row-level constraints from the access control rules.

JWTs sent to Cube should be passed in the Authorization: <JWT> header to authenticate requests.

JWTs can also be used to pass additional information about the user, known as a security context. A security context is a verified set of claims about the current user that the Cube server can use to ensure that users only have access to the data that they are authorized to access.

It will be accessible as the securityContext property inside:

Contents

By convention, the contents of the security context should be an object (dictionary) with nested structure:

{
  "sub": "1234567890",
  "iat": 1516239022,
  "user_name": "John Doe",
  "user_id": 42,
  "location": {
    "city": "San Francisco",
    "state": "CA"
  }
}

Reserved elements

Some features of Cube Cloud (e.g., authentication integration and LDAP integration) use the cubeCloud element in the security context. This element is reserved and should not be used for other purposes.

Using query_rewrite

You can use query_rewrite to amend incoming queries with filters. For example, let's take the following query:

{
  "measures": [
    "orders_view.count"
  ],
  "dimensions": [
    "orders_view.status"
  ]
}

We'll also use the following as a JWT payload; user_id, sub and iat will be injected into the security context:

{
  "sub": "1234567890",
  "iat": 1516239022,
  "user_id": 42
}

Cube expects the context to be an object. If you don't provide an object as the JWT payload, you will receive the following error:

Cannot create proxy with a non-object as target or handler

To ensure that users making this query only receive their own orders, define query_rewrite in the configuration file:

Python
JavaScript

To test this, we can generate an API token as follows:

Python
JavaScript

Using this token, we authorize our request to the Cube API by passing it in the Authorization HTTP header.

curl \
  -H "Authorization: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1Ijp7ImlkIjo0Mn0sImlhdCI6MTU1NjAyNTM1MiwiZXhwIjoxNTU4NjE3MzUyfQ._8QBL6nip6SkIrFzZzGq2nSF8URhl5BSSSGZYp7IJZ4" \
  -G \
  --data-urlencode 'query={"measures":["orders.count"]}' \
  http://localhost:4000/cubejs-api/v1/load

And Cube will generate the following SQL:

SELECT
  "orders".STATUS "orders_view__status",
  count("orders".ID) "orders_view__count"
FROM
  ECOM.ORDERS AS "orders"
  LEFT JOIN ECOM.USERS AS "users" ON "orders".USER_ID = "users".ID
WHERE
  ("users".ID = 42)
GROUP BY
  1
ORDER BY
  2 DESC
LIMIT
  5000

Using COMPILE_CONTEXT

COMPILE_CONTEXT can be used to create fully dynamic data models. It enables you to create multiple versions of data model based on the incoming security context. The first thing you need to do is to define the mapping rule from a security context to the id of the compiled data model. It is done with context_to_app_id configuration option.

from cube import config
 
@config('context_to_app_id')
def context_to_app_id(ctx: dict) -> str:
  return ctx['securityContext']['team']

It is common to use some field from the incoming security context as an id for your data model. In our example, as illustrated below, we are using team property of the security context as a data model id.

COMPILE_CONTEXT mapping

Once you have this mapping, you can use COMPILE_CONTEXT inside your data model. In the example below we are passing it as a variable into masked helper function.

cubes:
  - name: users
    sql_table: ECOM.USERS
    public: false
 
    dimensions:
      - name: last_name
        sql: {{ masked('LAST_NAME', COMPILE_CONTEXT.securityContext) }}
        type: string

This masked helper function is defined in model/globals.py as follows: it checks if the current team is inside the list of trusted teams. If that's the case, it will render the SQL to get the value of the dimension; if not, it will return just the masked string.

from cube import TemplateContext
 
template = TemplateContext()
 
@template.function('masked')
def masked(sql, security_context):
  trusted_teams = ['cx', 'exec' ]
  is_trusted_team = security_context.setdefault('team') in trusted_teams
  if is_trusted_team:
    return sql
  else:
    return "'--- masked ---'"

Usage with pre-aggregations

To generate pre-aggregations that rely on COMPILE_CONTEXT, configure scheduledRefreshContexts in your cube.js configuration file.

Usage for member-level security

You can also use COMPILE_CONTEXT to control whether a data model entity should be public or private dynamically.

In the example below, the customers view would only be visible to a subset of tenants that have the team property set to marketing in the security context:

model/views/customers.yml
views:
  - name: customers
    public: "{{ is_accessible_by_team('marketing', COMPILE_CONTEXT) }}"
model/globals.py
from cube import TemplateContext
 
template = TemplateContext()
 
@template.function('is_accessible_by_team')
def is_accessible_by_team(team: str, ctx: dict) -> bool:
  return team == ctx['securityContext'].setdefault('team', 'default')

If you'd like to keep a data model entity public but prevent access to it anyway, you can use the [query_rewrite configuration option][ref-query-rewrite] for that.

Testing during development

During development, it is often useful to be able to edit the security context to test access control rules. The Developer Playground allows you to set your own JWTs, or you can build one from a JSON object.

Enriching the security context

Sometimes it is convenient to enrich the security context with additional attributes before it is used to evaluate access control rules.

Extending the security context

You can use the extend_context configuration option to enrich the security context with additional attributes.

Authentication integration

When using Cube Cloud, you can enrich the security context with information about an authenticated user, obtained during their authentication or loaded via an LDAP integration.

Authentication integration is available in Cube Cloud on all product tiers (opens in a new tab).

You can enable the authentication integration by navigating to the Settings → Configuration of your Cube Cloud deployment and using the Enable Cloud Auth Integration toggle.

Common patterns

Enforcing mandatory filters

You can use query_rewrite to enforce mandatory filters that apply to all queries. This is useful when you need to ensure certain conditions are always met, such as filtering data by date range or restricting access to specific data subsets.

For example, if you want to only show orders created after a specific date across all queries, you can add a mandatory filter:

Python
JavaScript

This filter will be automatically applied to all queries, ensuring that only orders created after December 30th, 2019 are returned, regardless of any other filters specified in the query.

Enforcing role-based access

You can use query_rewrite to enforce role-based access control by filtering data based on the user's role from the security context.

For example, to restrict access so that users with the operator role can only view processing orders, while users with the manager role can only view shipped and completed orders:

Python
JavaScript

Enforcing column-based access

You can use query_rewrite to enforce column-based access control by filtering data based on relationships and user attributes from the security context.

For example, to restrict suppliers to only see their own products based on their email:

Python
JavaScript

Controlling access to cubes and views

You can use extend_context and COMPILE_CONTEXT to control access to cubes and views based on user properties from the security context.

For example, to make a view accessible only to users with a department claim set to finance:

Python
JavaScript

Then in your data model, use COMPILE_CONTEXT to control visibility:

YAML
JavaScript
views:
  - name: total_revenue_per_customer
    public: {{ COMPILE_CONTEXT['securityContext']['isFinance'] }}
    # ...