This section explains how to configure dual authentication for Cube Cloud, allowing your Cube Cloud instance to support both JWK-based authentication (for customer applications) and secret-based authentication (for the Embeddable backend).
CUBEJS_JWK_URL
: Set this to the JWKS endpoint URL from your identity provider. For example, Auth0 provides the following URL:
https://{yourDomain}/.well-known/jwks.json
auth.js
file to the root of the cube cloud folder (see below for example).cube.js
file to the root of the cube cloud folder (see below for example).package.json
file to the root of the cube cloud folder. This file lists the required dependencies (jsonwebtoken
and jwks-rsa
- see below for example).auth.js
, cube.js
, and package.json
files to your Cube Cloud environment.CUBEJS_JWK_URL
and the validateJwt
function in auth.js
to authenticate the request using the provided JWT and the JWKS from your identity provider.Request-Origin: embeddable
header. Cube Cloud will then use the CUBEJS_API_SECRET
and the validateEmbeddableJwt
function in auth.js
to authenticate the request using the shared secret key.Request-Origin
header in its requests to Cube Cloud.This dual authentication setup provides flexibility and security for your Cube.js integration, allowing you to use both JWK-based and secret-based authentication methods as needed.
This is an example auth.js
file. Copying and pasting this file probably won’t work for you (unless you have identical environment variable names), but it’s a good way to get started!
const jwt = require("jsonwebtoken");
const jwksRsa = require('jwks-rsa'); // jwks-rsa library handles caching of the JWKS internally
class AuthHandlerError extends Error {
constructor(status, message) {
super(message);
this.status = status;
}
}
const jwkUrl = process.env.CUBEJS_JWK_URL;
const jwksClient = jwksRsa({
jwksUri: jwkUrl
});
const secretKey = process.env.CUBEJS_API_SECRET;
const customCheckAuth = async (req, auth) => {
if (auth) {
if (req.get('Request-Origin') === 'embeddable') {
validateEmbeddableJwt(auth, req); // Secret key authentication
console.debug("validateEmbeddableJwt JWT verification successful.")
} else {
await validateJwt(auth, req); // JWK authentication
console.debug("validateJwt JWT verification successful.")
}
} else {
throw new AuthHandlerError(403, "Authentication required");
}
};
const validateEmbeddableJwt = (auth, req) => {
try {
const decoded = jwt.verify(auth, secretKey, {algorithms: ['HS256']});
req.securityContext = decoded;
} catch (err) {
throw new AuthHandlerError(403, `validateEmbeddableJwt JWT validation failed: ${err.message}, ${auth}`);
}
};
const validateJwt = async (auth, req) => {
try {
console.log("Starting JWT validation...");
const getKey = (header, callback) => {
console.debug("Fetching signing key with kid:", header.kid);
jwksClient.getSigningKey(header.kid, (err, key) => {
if (err) {
console.error("Error getting signing key:", err);
callback(err, null);
return;
}
console.debug("Signing key:", key);
const signingKey = key.getPublicKey();
callback(null, signingKey);
});
};
const decoded = await new Promise((resolve, reject) => {
jwt.verify(auth, getKey, {algorithms: ["RS256"]}, (err, decoded) => {
if (err) {
reject(new AuthHandlerError(403, `jwt.verify, JWT validation failed: ${err.message}`));
} else {
resolve(decoded);
}
});
});
console.debug("JWT verification successful. Decoded payload:", decoded);
req.securityContext = decoded;
} catch (err) {
throw new AuthHandlerError(403, `validateJwt JWT, validation failed: ${err.message}`);
}
};
module.exports = {customCheckAuth, AuthHandlerError};