Edit this page

Security

Cube.js uses JSON Web Tokens (JWT) which passed in Authorization header for requests' authorization and also for passing additional user context, which could be used in the USER_CONTEXT object in the Data Schema. Authorization header is parsed and set to authInfo variable which is also can be used for Multitenancy.

Cube.js tokens are designed to work in micro services environment. Typical use case would be:

  1. There's web server that serves HTML with JS client code that calls cube.js.
  2. Web server should generate expirable cube.js tokens and incorporate them as part of HTML or send it over XHR request in exchange of session cookie or other security credentials.
  3. JS Client code uses token to call cube.js server API.

If you are using REST API you need pass API Token via the Authorization Header. Cube.js Javascript client accepts auth token as a first argument to cubejs(authToken, options) function.

In the development environment the token is not required for authorization, but you can still use it to pass a security context.

Cube.js also supports Transport Layer Encryption (TLS) using Node.js native packages. For more information, see Enabling TLS.

Auth token is generated based on your API secret. Cube.js CLI generates API Secret on app creation and saves it in .env file as CUBEJS_API_SECRET variable.

You can generate two types of tokens:

  • Without security context. It implies same data access permissions for all users.
  • With security context. User or role-based security models can be implemented using this approach.

It is considered best practice to use exp expiration claim to limit life time of your public tokens. Learn more at JWT docs.

You can use a Cube.js CLI token command to generate an API token.

$ cubejs token -e TOKEN-EXPIRY -s SECRET -p FOO=BAR

However it is handy to create an API token with CLI command for testing purposes, we strongly recommend to programmatically generate tokens in production.

You can find a library for JWT generation for your programming language here.

Below you can find an example on how to generate an API token in Node.js with jsonwebtoken package:

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

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

Most often generation of tokens should be served as protected url:

app.use((req, res, next) => {
  if (!req.user) {
    res.redirect('/login');
    return;
  }
  next();
});

app.get('/auth/cubejs-token', (req, res) => {
  res.json({
    token: jwt.sign({ u: req.user }, process.env.CUBEJS_API_SECRET, { expiresIn: '1d' })
  })
})

Then fetched on client side as:

let apiTokenPromise;

const cubejsApi = cubejs(() => {
  if (!apiTokenPromise) {
    apiTokenPromise = fetch(`${API_URL}/auth/cubejs-token`)
      .then(res => res.json()).then(r => r.token)
  }
  return apiTokenPromise;
}, {
  apiUrl: `${API_URL}/cubejs-api/v1`
});

Security context can be provided by passing u param for payload. For example if you want to pass user id in security context you can create token with payload:

{
  "u": { "id": 42 }
}

In this case { "id": 42 } object will be accessible as USER_CONTEXT in the Cube.js Data Schema.

Consider the following example. We want to show orders only for customers, who owns these orders. orders table has a user_id column, which we can use to filter the results.

cube(`Orders`, {
  sql: `SELECT * FROM public.orders WHERE ${USER_CONTEXT.id.filter('user_id')}`,

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

Now, we can generate an API Token with user ID:

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

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

Using this token we can sign our request to Cube.js Backend.

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

And the following SQL will be generated by Cube.js.

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

Cube.js server package supports transport layer encryption.

By setting the environment variable CUBEJS_ENABLE_TLS to true (CUBEJS_ENABLE_TLS=true), @cubejs-backend/server expects an argument to its listen function specifying the tls encryption options. The tlsOption object must match Node.js' https.createServer([options][, requestListener]) option object.

This enables you to specify your TLS security directly within the Node process without having to rely on external deployment tools to manage your certificates.

const fs = require("fs-extra");
const CubejsServer = require("@cubejs-backend/server");

var tlsOptions = {
  key: fs.readFileSync(process.env.CUBEJS_TLS_PRIVATE_KEY_FILE),
  cert: fs.readFileSync(process.env.CUBEJS_TLS_PRIVATE_FULLCHAIN_FILE),
};

const cubejsServer = new CubejsServer();

cubejsServer.listen(tlsOptions).then(({ tlsPort }) => {
  console.log(`🚀 Cube.js server is listening securely on ${tlsPort}`);
});

Notice that the response from the resolution of listen's promise returns more than just the port and the express app as it would normally do without CUBEJS_ENABLE_TLS enabled. When CUBEJS_ENABLE_TLS is enabled, cubejsServer.listen will resolve with the following:

  • port {number} The port at which CubejsServer is listening for insecure connections for redirection to HTTPS, as specified by the environment variable PORT. Defaults to 4000.
  • tlsPort {number} The port at which TLS is enabled, as specified by the environment variable TLS_PORT. Defaults to 4433.
  • app {Express.Application} The express App powering CubejsServer
  • server {https.Server} The https Server instance.

The server object is especially useful if you want to use self-signed, self-renewed certificates.

Self-signed, self-renewed certificates are useful when dealing with internal data transit, like when answering requests from private server instance to another private server instance without being able to use an external DNS CA to sign the private certificates. Example: EC2 to EC2 instance communications within the private subnet of a VPC.

Here is an example of how to do leverage server to have self-signed, self-renewed encryption:

const CubejsServer = require("@cubejs-backend/server");

const {
  createCertificate,
  scheduleCertificateRenewal,
} = require("./certificate");

async function main() {
  const cubejsServer = new CubejsServer();

  const certOptions = { days: 2, selfSigned: true };
  const tlsOptions = await createCertificate(certOptions);

  const ({ tlsPort, server }) = await cubejsServer.listen(tlsOptions);
  
  console.log(`🚀 Cube.js server is listening securely on ${tlsPort}`);
  
  scheduleCertificateRenewal(server, certOptions, (err, result) => {
    if (err !== null) {
      console.error(
        `🚨 Certificate renewal failed with error "${error.message}"`
      );
      // take some action here to notify the DevOps
      return;
    }
    console.log(`🔐 Certificate renewal successful`);
  });
}

main();

To generate your self-signed certificates, look into pem and node-forge.

Certificate Renewal using server.setSecureContext(options) is only available as of Node.js v11.x