Power BI Security and Row-Level Security
Right rows, right people, right places
Securing Power BI means controlling access at four layers: data source, model (row-level security via roles), report/workspace permissions, and sharing. RLS filters rows by the viewer's identity or attributes.
What you will learn
- Apply static and dynamic row-level security
- Layer RLS with workspace permissions and OLS
- Test RLS as a consumer before publishing
What is this?
Power BI security is layered: sources are accessed with credentials; the model can restrict rows via row-level security (RLS) using DAX roles (static like 'region = EU' or dynamic via USERPRINCIPALNAME matching a dimension); workspace/app permissions control artifact access; object-level security (OLS) hides columns. RLS is enforced server-side in the service, not in the visual.
Why do you need it?
- Regulatory and commercial walls require row and column restrictions.
- One shared dataset can serve many audiences if RLS is correct.
- Dynamic RLS means new users are secured without editing roles.
- Testing as a consumer catches leaking rules before regulators do.
Mental model
A club with a bouncer who reads your ID
RLS
Everyone walks into the same club (shared model) but the bouncer (DAX role) limits which tables (rows) you get by checking your ID (USERPRINCIPALNAME/attributes). Change the list once, every door checks it.
How it works
- Model roles contain DAX filter expressions; membership is users/groups.
- Static RLS: hard-coded filter ('region = "EU"').
- Dynamic RLS: filter references the viewer's identity or a mapping table (USERNAME()).
- The service applies roles at query time; DirectQuery/import both enforce.
- Workspace roles (Viewer etc.) and OLS layer above/below row rules.
Four layers of Power BI security.
Examples
Dynamic row-level security
RegionManager = VAR me = USERPRINCIPALNAME() RETURN FILTER( dim_manager, dim_manager[email] = me )-- then link fact data to that manager's allowed regions via the dim -- Static alternativeEUOnly = 'dim_region'[region] = "EU"Dynamic RLS reads who's logged in and matches their mapping row, so adding a new manager's mapping secures them without role edits.
In production
- A finance dataset: 'RegionManager' role shows VPs only their regions' rows via a manager map.
- Support data shares one dataset, restricting rows by team membership tags.
- Marketing hides strategic columns with OLS while sharing the dataset engine-wide.
Best practices
- Model RLS on mapping tables (dynamic) with documented role names.
- Test RLS as a viewer using the 'View as role' feature against expected numbers.
- Keep RLS in the model, not in report-level filters users can clear.
- Audit who the roles grant access to and review membership quarterly.
Common mistakes
- Using report page filters as 'security' — users can clear them.
- Static roles duplicated per region instead of one dynamic mapping.
- Testing only in Desktop, where USERNAME() may differ from the service.
- Over-wide workspace permissions bypassing RLS intent for exports.
Common issues
User sees another region's rowsRLS role missing membership or the mapping row lacks their email
Add the user to the role; correct the mapping; re-test with View-as.
RLS works in Desktop but not in the appApp audience membership differs from role membership
Verify app audience and dataset role membership align.
Debugging tips
- Blank rows under a role → the role filter matches nothing; check mapping email/cases.
- 'View as role' passes but service fails → check membership and USERNAME() format.
- Export leaks → block exports or enforce RLS on the export source.
Interview checkpoint
Hands-on lab
- Build a manager→region mapping for dynamic RLS
- Simulate per-user row access with SQL
- Draft and verify the DAX role logic textually
Design Dynamic Row-Level Security
45 min · advanced
Quick quiz
Where is row-level security enforced?
A report page filter is an acceptable substitute for RLS.