Skip to main content
Version: 9.2

Record Level Security (RLS)

What is RLS and When is it Useful?

Record Level Security (RLS), also known as Row-Level Security, is a feature that restricts access to individual rows in your datasets. With RLS, each user only sees the data they are authorized to view, even when all records are stored in a single dataset.

Scenario Example:
Suppose you are embedding a sales chart in your application for multiple tenants. You have two users:

  • User A: Should only see sales data for North America.
  • User B: Should only see sales data for Europe.

With RLS, you can use a single dataset containing sales for all countries and all tenants, but each user will only see the data they are permitted to access.

Sales by Country and Tenant:
Shows a dataset with sales by country and tenant, and two users each seeing only their allowed country’s data in the embedded chart. Sales by Country and Tenant

Note: RLS is never applied in "Composer Mode". All datasets, whether or not they have RLS security groups defined, are effectively "open" when you are logged in to Composer.


How to Configure RLS in Qrvey

In Qrvey, RLS is implemented as a filter that is embedded within the authentication/authorization token for the widget, called the qvToken. When you create this token, you specify a set of filters under a property called record_permissions. When the token is passed to the widget, these filters are automatically applied to all data shown in that widget, ensuring users only see the data they are permitted to access. For security, we recommend generating the qvToken in your backend (server-side) service so it cannot be tampered with by end users.

Step 1: Enable RLS on the Dataset

A data admin or builder enables Row Level Security on the dataset in Qrvey Composer.

  • You can enable RLS on one or more columns (e.g., country, tenant_id).
  • When enabling RLS, you must provide a security name for each column. This name will be used later to filter data for each user.
  • After enabling RLS and clicking Apply Changes, the dataset is ready for RLS.

Enabling RLS on a dataset:
Qrvey Composer Dataset UI where a security column is being enabled and a security name is being set. Enabling RLS on a dataset


Step 2: Authentication and Authorization for Widgets

To enforce RLS when embedding widgets, you need to authenticate users and authorize their data access:

  1. Create a Security Token (qvToken):
    • In your application (the one embedding the widget), call the Qrvey "Create Token" API to generate a security token.
    • Best Practice: Call this API from your backend for security reasons.
  2. Pass the Token to the Frontend:
    • Send the generated token to your frontend.
    • Attach the token to the widget’s JSON config using the qvToken parameter.

API Flow to generate a token:
Customer app frontend requests token from backend → Backend calls Qrvey API → Backend returns token to frontend → Frontend embeds widget with token → Qrvey widget backend validates token and filters data. API Flow to generate a token


RLS Permissions Schema

The permissions parameter in the security token defines which records a user can access.

Attribute Definitions

  • permissionsArray<Object>Required – Each object defines RLS permissions for a given dataset.

    • dataset_idString || Array<String>Required – Id of dataset to apply RLS to. Use * to match all datasets.

    • operatorString ("AND" || "OR")Optional – Defaults to AND. Determines how to combine record_permissions.

    • record_permissionsArray<Object>Required – List of record filter objects specifying the permitted values and/or value ranges for each dataset column where an RLS security group is defined.

      • security_nameStringRequired – Name of the security group defined for the dataset column.

      • validation_typeStringOptional – Determines how to evaluate the values. Defaults to EQUAL.
        Options:
        EQUAL, NOT_EQUAL, CONTAIN, NOT_CONTAIN, RANGE, NOT_RANGE, BETWEEN,
        GREATER_THAN, GREATER_THAN_OR_EQUAL, LESS_THAN, LESS_THAN_OR_EQUAL,
        START_WITH, NOT_START_WITH, END_WITH, NOT_END_WITH, IS_EMPTY, IS_NOT_EMPTY.

      • valuesArray<String>Required – List of permitted values. If set to EQUAL, use * for unrestricted access.

      • group_valueStringOptional – For date columns only. Defaults to DAY.
        Options:
        SECOND, MINUTE, HOUR, DAY, WEEK, MONTH, QUARTER, YEAR, etc...


Example: Generating and using a Security Token with RLS Permissions

Node.JS Backend sample for Generating a Token

//Works with Node.JS v18 or higher
const express = require('express');
const cors = require('cors');
const app = express();
const port = 3000;

const {
API_KEY,
DOMAIN,
ORIGIN
} = process.env;

const corsOptions = {
origin: ORIGIN, // Allow requests only from this origin
methods: 'GET,POST', // Allowed HTTP methods
credentials: true, // Allow sending cookies and authentication headers
optionsSuccessStatus: 200 // Some legacy browsers (IE11, various SmartTVs) choke on 204
};

app.use(cors(corsOptions));

app.get('/api/get-jwt-token', (req, res) => {
const url = DOMAIN + '/devapi/v4/core/login/token';
const config = {
// ... Top-level of qvToken config. This will filter all chartdata for dataset1 by tenantId=123 and country IN ("USA", "CANADA","MEXICO")
"permissions": [
{
"dataset_id": "dataset1",
"operator": "OR",
"record_permissions": [
{
"security_name": "tenant-id-rls",
"validation_type": "EQUAL",
"values": [
"123"
]
},
{
"security_name": "country-rls",
"validation_type": "EQUAL",
"values": [
"USA",
"CANADA",
"MEXICO"
]
}
]
}
]
}
const options = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
'x-api-key': API_KEY
},
body: JSON.stringify(config)
};
try {
const response = await fetch(url, options);
const data = await response.json();
console.log(data.token);
res.json({ message: 'Function executed successfully', token: data.token });
} catch (error) {
console.error(error);
}
});

app.listen(port, () => {
console.log(`Node.js server listening at http://localhost:${port}`);
});

Using the token in Dashboard Widget config

<html>
<head></head>
<body>
<h2>The Dashboard</h2>
<h3>Record level security via QV Token</h3>
</br>
</br>
<div id='widget-container'></div>

<script>
// A frontend call to your backend, which fetches & forwards the qvToken.
async function fetchToken() {
try {
const response = await fetch('https://localhost:3000/api/get-jwt-token');
const data = await response.json();
console.log("RESPONSE:", data);
return data.token;
} catch(error) {
console.log(error)
}
}

// Injects widget with global settings.
async function embedWidget() {
let token = await fetchToken();
let widgetContainer = document.querySelector("#widget-container");
window["qrvey-dashboard-config"] = { // Setting global settings
"domain": "DOMAIN",
"qvToken": token,
};
let dashboard = document.createElement("qrvey-dashboard");
dashboard.setAttribute("settings", "qrvey-dashboard-config");
widgetContainer.append(dashboard);
}

embedWidget();
</script>
<script type="module" src="https://demo.qrvey.com/qrvey-dashboard/qrvey-dashboard/qrvey-dashboard.esm.js"></script>
</body>
</html>

Troubleshooting

Scenario 1: Chart Shows "No data found"

If your chart displays a "No data found" message:

  • Ensure that you are setting the security filters correctly in the record_permissions object of the security token.
  • All security columns selected in the dataset must have a value defined in record_permissions.
  • If you want a column to be unrestricted, use * as the value for that column.

Scenario 2: Chart Shows All Data (No Filtering)

If your chart is not filtering data and shows all records:

  • Verify that security columns are enabled on the dataset in Qrvey Composer.
  • Make sure you clicked the Apply Changes button after configuring security columns.
  • Check your security token: if you use * for the values in record_permissions, the filter will allow all data for that column. Only use * if you intend to grant unrestricted access.

Learn More