Skip to main content
Version: 9.2

Row-Level Security Basics

Use row-level security to protect and filter rows of data so users only see the records they are allowed to see. This control can help you to manage multi-tenant or role-sensitive applications.

Overview

Row-level security is a server-side security filter that pre-filters records before a user loads a widget. It is most commonly used to enforce tenant isolation in a commingled dataset architecture.

To enable row-level security, mark the row-level security columns in Qrvey Composer, then securely pass the permissions block from your backend when you generate the widget configuration.

Core Principles

Row-level security embodies the following core principles:

  • Restrictions should be applied so the end user is not aware filters are enforced.
  • End users should not be able to see or modify the row-level security filters.
  • Row-level security values should be treated as sensitive and stored on the server side.

Tutorial Video

The following tutorial video shows how to set up row-level security.

Tags: Embedding, RLS, Authorization

General Configuration

Row-level security requires the following general configuration steps.

Step 1: Designate Dataset Fields as Row-Level Security Fields

  1. Open your dataset in Qrvey Composer.

  2. Select the options menu for a column (for example, ProductID), then select Enable Record Level Security.

    Row-Level security options menu

  3. Enter a security name (for example, product_id) to be referenced in the widget JSON.

    • It can match the column name for clarity.
    • Do not include spaces or special characters. Use underscores if needed.
  4. Apply your changes.

Step 2: Pass Row-Level Security Values When Embedding

When you embed a widget, you must pass the row-level security values in the JSON permissions block of the widget configuration. Row-level security is nested under the permissions property. The structure is an array because you might need to define row-level security for multiple datasets.

The following code snippet shows the minimum structure required to pass row-level security:

app.get('/api/get-jwt-token', async (req, res) => {
try {
const response = await axios.post('https://demo.qrvey.com/devapi/v4/core/login/token', {
version: 2,
expiresIn: '1w',
appId: 'i38McJ02G',
userId: 'HPESUG1wR',
clientId: 'HPESUG1wR',
orgId: 'org:0',
permissions: [
{
dataset_id: "dataset1",
record_permissions: [
{
security_name: "product_id",
validation_type: "EQUAL",
values: ["344"]
}
]
}
]
});
  • Permissions are displayed as an array of objects. Each object represents one dataset to filter.
  • Inside each dataset object, the record_permissions array contains security filter objects.
  • Each security filter includes a security_name (set to the name of the row-level security column), operator, and values to filter by.
  • Each dataset can contain multiple row-level security columns.
  • Do not include Boolean operators (AND/OR) when passing a single value. Keep values minimal and clear.

Placement of Row-Level Security Code

Row-level security values are sensitive. Do not embed the raw permissions block directly in the client-side code. Because the widget configuration is visible on the client, use the following techniques:

  • Generate the widget configuration and sensitive properties from your back end. Do not pass row-level security configuration in the widget's configuation object.
  • Include the row-level security configuration in the body of your JWT token that the client can pass to the embedded component.
  • Make sure the client only receives the encrypted configuration, not raw row-level security values.

Step 3: Locate the Dataset ID

The dataset_id used in the permissions block serves as the internal Qrvey dataset identifier. It can be found in the URL of the dataset design page in Composer:

https://example.qrvey.com/#/application/appId/data-uploads/dataset_id/

Copy that dataset ID into the widget permissions block.

Step 4: Test Row-Level Security

After you add the permissions block to your server-generated widget configuration, restart your backend service if needed and load the embedded widget. If configured correctly, the embedded view displays only the records allowed by the row-level security values you passed.

Example test:

  1. Pass a single product ID (for example, product_id = 344) in the permissions block.
  2. Load the widget. The widget should render only the records for product 344.

Row-Level Security in Composer

Row-level security does not apply in Composer. Within Composer, you still see all values. Row-level security takes effect in the embedded widget when it receives the permissions block that contains filter values. This means developers can design and test dashboards in Composer using regular filters.

Tip: To permit the embedded widget to show all records during testing, pass a wildcard for all security columns. Use an asterisk to represent a wildcard:

"values": ["*"]

This lets embedded views behave like Composer until you start passing explicit row-level security values.

Best Practices

  • Designate row-level security fields in the dataset before applying any changes.
  • Store row-level security values on the server side. Never expose raw row-level security values on the client side.
  • Embed only the encrypted widget configuration using JWT.
  • Test by embedding and running client-side filters to confirm only permitted values appear.
  • Use the asterisk wildcard for temporary testing to mimic Composer behavior.