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.js to get security context claims to evaluate access control rules. Inbound JWTs are decoded and verified using industry-standard JSON Web Key Sets (JWKS).

For access control or authorization, Cube.js allows you to define granular access control rules for every cube in your data schema. Cube.js 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.js 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.js 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:

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

{
  "dimensions": ["Orders.status"],
  "measures": ["Orders.count", "Orders.total"],
  "timeDimensions": [
    {
      "dimension": "Orders.createdAt",
      "dateRange": ["2015-01-01", "2015-12-31"],
      "granularity": "month"
    }
  ]
}

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": 131
}

Cube.js 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 queryRewrite in the cube.js configuration file:

module.exports = {
  queryRewrite: (query, { securityContext }) => {
    // Ensure `securityContext` has an `id` property
    if (!securityContext.user_id) {
      throw new Error('No id found in Security Context!');
    }

    query.filters.push({
      member: 'Orders.userId',
      operator: 'equals',
      values: [securityContext.user_id],
    });

    return query;
  },
};

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

const jwt = require('jsonwebtoken');
const CUBE_API_SECRET = 'secret';

const cubejsToken = jwt.sign({ user_id: 42 }, CUBEJS_API_SECRET, {
  expiresIn: '30d',
});

Using this token, we authorize our request to the Cube.js 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.js will generate the following SQL:

SELECT
  count(*) "orders.count"
  FROM (
    SELECT * FROM public.orders WHERE user_id = 42
  ) AS orders
LIMIT 10000

In the example below user_id, company_id, sub and iat will be injected into the security context and will be accessible in both the Security Context and COMPILE_CONTEXT global variable in the Cube.js Data Schema.

COMPILE_CONTEXT is used by Cube.js at schema compilation time, which allows changing the underlying dataset completely; the Security Context is only used at query execution time, which simply filters the dataset with a WHERE clause.

{
  "sub": "1234567890",
  "iat": 1516239022,
  "user_id": 131,
  "company_id": 500
}

With the same JWT payload as before, we can modify schemas before they are compiled. The following schema will ensure users only see results for their company_id in a multi-tenant deployment:

const {
  securityContext: { company_id },
} = COMPILE_CONTEXT;

cube(`Orders`, {
  sql: `SELECT * FROM ${company_id}.orders`,

  measures: {
    count: {
      type: `count`,
    },
  },
});

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

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.

Did you find this page useful?