# How to Implement Column-Level Security and Dataset-Level Access Control in Apache Superset

> **Column-level security in Apache Superset is enforced through Row Level Security (RLS) filters that inject SQL clauses into queries, while dataset-level access control uses FAB permissions evaluated via `get_dataset_access_fil...

- Repository: [The Apache Software Foundation/superset](https://github.com/apache/superset)
- Tags: 
- Published: 2026-03-03

---

**Column-level security in Apache Superset is enforced through Row Level Security (RLS) filters that inject SQL clauses into queries, while dataset-level access control uses FAB permissions evaluated via `get_dataset_access_filters()` in the security manager.**

Apache Superset implements granular data protection through two complementary mechanisms living in the security manager layer. This guide explains how to configure both dataset-level access control and column-level security using the native `RowLevelSecurityFilter` system and permission model. You will learn to leverage the REST API, Python client, and underlying source code in [`superset/security/manager.py`](https://github.com/apache/superset/blob/main/superset/security/manager.py) to enforce fine-grained access policies.

## Understanding the Two Security Layers

Superset separates data access into dataset-level permissions and column-level (row-level) filters. Both mechanisms are evaluated during query construction in SQL Lab, Explore, and chart rendering to ensure the database never returns unauthorized data.

### Dataset-Level Access Control

Dataset-level security protects entire tables or virtual datasets using Flask-AppBuilder (FAB) permissions. The `SupersetSecurityManager.can_access_datasource()` method (line 649 in [`superset/security/manager.py`](https://github.com/apache/superset/blob/main/superset/security/manager.py)) calls `raise_for_access()` which uses `get_dataset_access_filters()` from [`superset/utils/filters.py`](https://github.com/apache/superset/blob/main/superset/utils/filters.py) to generate a SQLAlchemy `OR` clause. This clause checks if the user owns a database permission, catalog permission, schema permission, or explicit datasource permission string such as `"[my_db].[my_table](id:42)"`.

### Column-Level Security via Row Level Security (RLS)

Column-level restrictions are implemented through `RowLevelSecurityFilter` objects stored in the `row_level_security_filters` table. During query execution (lines 2728-2849 in [`superset/security/manager.py`](https://github.com/apache/superset/blob/main/superset/security/manager.py)), the security manager joins the user’s roles with RLS filters via the `rls_filter_roles` association table and injects the filter’s `clause` text into the `WHERE` clause. The `filter_type` field (`REGULAR` or `BASE`) determines whether the clause is `AND`-ed with other filters or serves as a default base filter when no regular filter exists.

## Implementing Dataset-Level Access Control

Every `SqlaTable` computes a permission string via `get_perm()` in [`superset/connectors/sqla/models.py`](https://github.com/apache/superset/blob/main/superset/connectors/sqla/models.py). To grant access, assign this permission to a role.

1. **Identify the dataset permission** string through the UI at **Datasets → List → … → Permissions** or programmatically via `DatasetDAO.find_by_id(id).perm`.
2. **Grant the permission** to a role using the **Security → List Roles** UI or the REST API.
3. **Validate access** by attempting to query the dataset; unauthorized users receive a `DATASOURCE_SECURITY_ACCESS_ERROR` defined in [`superset/exceptions.py`](https://github.com/apache/superset/blob/main/superset/exceptions.py).

### Granting Dataset Permissions via REST API

```bash

# Authenticate and obtain JWT

TOKEN=$(curl -s -X POST http://localhost:8088/api/v1/security/login \
      -H "Content-Type: application/json" \
      -d '{"username":"admin","password":"admin"}' | jq -r .access_token)

# Grant permission for dataset id 42 to role id 5

curl -X POST "http://localhost:8088/api/v1/security/role/5/permissions" \
     -H "Authorization: Bearer $TOKEN" \
     -H "Content-Type: application/json" \
     -d '{"permissions":["[my_db].[my_table](id:42)"]}'

```

The helper `get_dataset_access_filters()` in [`superset/utils/filters.py`](https://github.com/apache/superset/blob/main/superset/utils/filters.py) (lines 24-44) automatically combines permissions. It grants access if the user holds `all_datasource_access`, explicit `datasource_access`, or broader `catalog_access`, `schema_access`, or `database_access` permissions covering the dataset’s location.

## Implementing Column-Level Security with RLS

RLS filters allow you to inject arbitrary SQL expressions referencing specific columns into every query. This effectively restricts which rows and column values users can see based on their role memberships.

1. **Create the RLS filter** with a `clause` targeting specific columns (e.g., `department = 'finance'`).
2. **Assign to roles** via the **Security → Row Level Security** UI or the REST API endpoint defined in [`superset/row_level_security/api.py`](https://github.com/apache/superset/blob/main/superset/row_level_security/api.py).
3. **Filter application** happens automatically during query building; regular filters are `AND`-ed together while base filters apply only when no regular filter exists for a user's roles.

### Adding RLS Filters via Python Client

```python
from superset import db
from superset.connectors.sqla.models import RowLevelSecurityFilter, SqlaTable
from superset.security.manager import security_manager
from superset import utils

# Identify target dataset

dataset = db.session.query(SqlaTable).filter_by(id=42).one()

# Create RLS filter restricting to finance department

rls = RowLevelSecurityFilter(
    clause="department = 'finance'",
    filter_type=utils.RowLevelSecurityFilterType.REGULAR,
    tables=[dataset],
)
db.session.add(rls)
db.session.flush()

# Associate with Finance role

finance_role = security_manager.find_role("Finance")
finance_role.row_level_security_filters.append(rls)
db.session.commit()

```

The `RowLevelSecurityFilter` model is defined at line 2082 in [`superset/connectors/sqla/models.py`](https://github.com/apache/superset/blob/main/superset/connectors/sqla/models.py). The security manager queries these filters and the `rls_filter_roles` association table during query construction to build the final SQL.

### Using Low-Level Filter Helpers

For custom integrations or middleware, use the filter utilities directly to respect dataset permissions:

```python
from superset.utils.filters import get_dataset_access_filters
from superset.connectors.sqla.models import SqlaTable
from sqlalchemy import select

def build_secure_query():
    table = SqlaTable.find_by_id(42)
    base_query = select([table.table])
    permission_filter = get_dataset_access_filters(SqlaTable)
    return base_query.where(permission_filter)

```

## Summary

- **Dataset-level access** relies on FAB permission strings evaluated by `get_dataset_access_filters()` in [`superset/utils/filters.py`](https://github.com/apache/superset/blob/main/superset/utils/filters.py), checking for `database_access`, `schema_access`, `catalog_access`, or explicit `datasource_access`.
- **Column-level security** uses `RowLevelSecurityFilter` entries stored in `row_level_security_filters`, with SQL clauses injected into queries by the logic in [`superset/security/manager.py`](https://github.com/apache/superset/blob/main/superset/security/manager.py) (lines 2728-2849).
- Both mechanisms support UI configuration, REST API management via [`superset/row_level_security/api.py`](https://github.com/apache/superset/blob/main/superset/row_level_security/api.py), and programmatic Python client access.
- RLS filters support `REGULAR` (additive `AND` clauses) and `BASE` (default fallback) types for complex security policies.
- Permission evaluation occurs during SQL generation, ensuring the database never sees unauthorized rows.

## Frequently Asked Questions

### What is the difference between dataset-level and column-level security in Superset?

Dataset-level security controls access to entire tables or virtual datasets through FAB permissions like `datasource_access` or `schema_access`, evaluated by `SupersetSecurityManager.can_access_datasource()`. Column-level security uses Row Level Security (RLS) filters that inject SQL `WHERE` clauses to restrict which rows and column values users see based on their roles.

### How does Superset enforce RLS filters during query execution?

When building a query, the security manager (around lines 2728-2849 in [`superset/security/manager.py`](https://github.com/apache/superset/blob/main/superset/security/manager.py)) selects applicable `RowLevelSecurityFilter` objects from the `row_level_security_filters` table by joining through the `rls_filter_roles` association table. It concatenates the `clause` strings with `AND` operators and injects them into the generated SQL `WHERE` clause before sending the query to the database.

### Can RLS filters restrict access to specific columns rather than just rows?

While RLS filters technically operate on rows, you can achieve column-level restrictions by filtering on column values that only exist in specific rows (e.g., `department = 'finance'`). The filter clause can reference any column in the table, allowing you to hide entire data segments containing sensitive column values from unauthorized roles. For true column masking, combine RLS with database-level views or calculated columns.

### Where is the dataset permission logic implemented in the Superset codebase?

The core permission evaluation resides in [`superset/security/manager.py`](https://github.com/apache/superset/blob/main/superset/security/manager.py) in the `can_access_datasource()` and `raise_for_access()` methods. The SQLAlchemy filter generation logic is in [`superset/utils/filters.py`](https://github.com/apache/superset/blob/main/superset/utils/filters.py) inside `get_dataset_access_filters()`. Permission strings are generated by `SqlaTable.get_perm()` in [`superset/connectors/sqla/models.py`](https://github.com/apache/superset/blob/main/superset/connectors/sqla/models.py), which also defines the `RowLevelSecurityFilter` model at line 2082.