Power BI
Intermediate 11 min read Power BI

Row-Level Security in Power BI

Scope the model by who is viewing it

Row-level security (RLS) limits which rows a viewer can see through static or dynamic roles. Dynamic roles read the user's identity against a lookup table to scope data per person.

row-level securityRLSrolesdynamic securityUSERPRINCIPALNAMEDAX rules
Before we start

What you will learn

  • Create a static role with a DAX rule
  • Build a dynamic role from a user-data lookup
  • Explain how RLS does and does not protect data
Introduction

What is this?

Row-level security applies a DAX filter to a table for a given role: a rule like [Region] = 'west' shows only west rows. Dynamic RLS swaps the literal for a function like USERPRINCIPALNAME() and matches the logged-in user's row in a permissions table. RLS governs viewing in the service; it is not a substitute for encryption, and it can be bypassed by a model author who rebuilds the dataset.

Motivation

Why do you need it?

  • Compliance (regional, per-client data separation) without duplicating datasets per group.
  • One certified model serves every team with per-person scoping.
  • Dynamic RLS updates when staff change, without editing reports.
  • Interview and platform questions about it are common in BI-adjacent roles.
Think about it

Mental model

Imagine

Keycards opening specific doors

Think of

RLS roles

Everyone enters the same building (the dataset), but your keycard (role) opens only the doors (rows) for your responsibility. A temptation to store -everything- behind one key means the security is decorative.

Mechanics

How it works

  • Define a role in the model (e.g. 'West Managers').
  • A DAX rule filters the fact or dimension table for that role.
  • Add users to the role in Power BI Service.
  • Dynamic rules read USERPRINCIPALNAME() or USERNAME() and join to a users-to-scope table.
  • Developers with Build permission to the dataset can override RLS by editing the model.

How a dynamic RLS rule scopes data.

User opens report
USERPRINCIPALNAME
permissions table
RLS filter
Visual renders
Hands-on

Examples

Dynamic RLS role rule

Dynamic RLS role rule
Role: Regional Manager
Security table: Permissions[user_email, Region]
Rule on Region table:
[Region] IN
LOOKUPVALUES( Permissions[Region],
Permissions[user_email],
USERPRINCIPALNAME() )

The rule looks up the caller's email in the permission table, returns the regions that user manages, and filters the Region dimension so every visual in the model reflects only those rows.

Real world

In production

  • A multinational retailer gives each country's managers a role filtering by country table, staff moves handled by the identity source.
  • A customer-success product scopes dashboards so each customer sees its own metrics, using a permission table updated from the CRM nightly.
  • A hospital keeps patient rows scoped to unit managers by a dynamic role matching their OU.
Guidelines

Best practices

  • Put RLS rules on dimension tables, not the fact, so every path benefits.
  • Use dynamic rules with a dedicated permissions table kept in sync by an external process.
  • Restrict dataset Build permissions so RLS cannot be bypassed by rebuilding the model.
  • Test roles as a non-admin user in the service before rollout.
Watch out

Common mistakes

  • Filtering only the fact while dimensions leak the full attribute list.
  • Hard-coding usernames in rules instead of a lookup table, creating drift.
  • Giving everyone 'member' access to a dataset that carries sensitive rows.
  • Forgetting that RLS never hides a downloaded .pbix whose model you can reopen.
Root cause analysis

Common issues

Error
A user sees all rows even after adding them to a role
Root cause

They were also assigned a role without restrictions or dataset-level viewer role

Fix

Check the user's membership in other roles and dataset sharing settings.

Error
Dynamic RLS returns blank for some users
Root cause

USERPRINCIPALNAME does not match the permission table entries

Fix

Normalize casing and trim both sides, then re-add missing names.

Error
RLS works on the report but not on a dashboard tile
Root cause

The tile predates the RLS change and is cached

Fix

Republish the dataset and refresh the dashboard tiles.

When things break

Debugging tips

  • In Desktop, click Role > View as role to simulate a viewer before publishing.
  • Check the permission table's join key matches USERPRINCIPALNAME byte-for-byte.
  • Verify that RLS auto-rules use a unique key (one row per user-scope pair).
  • Test at least two roles against one visual to confirm no data leak.
Interviews

Interview checkpoint

Test yourself

Quick quiz

0/2
1

Which function reads the identity of the current viewer in a dynamic RLS rule?

2

RLS completely prevents someone with the .pbix file from seeing restricted data.

Go deeper

Official documentation