What it does
RLS applies DAX filter expressions to table rows for members of a role, so different users querying the same semantic model see different data. It filters rows and only rows — it can't restrict access to tables, columns or measures, which is object-level security's job.
Key facts
- When a user maps to several roles, RLS filters are additive. Users see the union of what each role permits. There's no "once denied always denied" — a role granting
TRUE()on a table overrides a role denying it withFALSE(). - Microsoft's advice is to design a single role that grants everything one user needs, rather than composing permissions from multiple role memberships.
- Role membership is assigned after publishing, by the semantic model owner or a workspace admin. Members can be user accounts, security groups, distribution groups or mail-enabled groups — Microsoft recommends security groups.
- RLS filters are more efficient on dimension tables than fact tables, with well-designed relationships propagating the filter outwards. Filters only propagate through active relationships.
- Avoid
LOOKUPVALUEwhere a model relationship would do the same job — the relationship propagates RLS, the function doesn't. - Performance impact is measurable in Power BI Desktop: time the visuals with Performance Analyzer, then use View as on the Modeling ribbon to enforce a role and compare.
- On DirectQuery tables with relationships to other DirectQuery tables, RLS performance depends on source database indexing and persisted computed columns.
- DAX can't override RLS or even detect that it's applied. Where a calculation needs unfiltered totals — a share-of-all-regions ratio, say — you need a separate summary table or aggregation table that RLS doesn't filter.
- In the embed for your customers scenario the app supplies the effective identity username, and it can pass any value. Rules must be written so that unexpected values return no rows.
- For live connections to Azure AS or SQL Server AS, RLS is enforced by Analysis Services, not by Power BI — except for accounts with admin privileges.
When to use / skip
Use static roles when the security shape is small, stable and expressible as a handful of groups — three regions, four business units, a role each. It's readable, testable, and someone other than you can maintain it.
Move to dynamic RLS the moment the shape is per-person, hierarchical, or changes often. A security table mapping user principal name to permitted dimension keys, joined into the model, with a single role filtering on USERPRINCIPALNAME(), is the pattern that scales. It also moves security maintenance out of the model and into data, which is where it belongs — a new salesperson becomes a row, not a deployment.
Be sceptical of RLS as a substitute for separate models. If two audiences need genuinely different data with different definitions, RLS is doing the job of architecture and it will show up as complexity nobody can reason about. And be very clear with the client: RLS filters rows in the model. It doesn't stop anyone exporting the rows they can see.
Configuration decisions
- Static or dynamic, and if dynamic, where the security table comes from and who maintains it. HR system, Entra ID, or a spreadsheet somebody updates — all three exist in the wild, and only two of them are acceptable.
- Whether to filter on
USERPRINCIPALNAME()orUSERNAME(). UPN is the reliable choice in the service; be deliberate about it rather than copying a sample. - Which table carries the filter. Dimension over fact, every time, with relationships doing the propagation.
- How hierarchy is modelled — a manager seeing their whole reporting line usually needs a bridge table or a path pattern, and that decision has real performance consequences.
- Whether any calculation needs to see beyond the user's filter, and therefore whether a summary or aggregation table is part of the design.
- How roles are tested and by whom, and whether that test is repeated after every model change.
Gotchas
- Additive roles catch everyone once. A user in both a restrictive role and a permissive one gets the permissive result, which is the opposite of what security-minded people assume.
LOOKUPVALUEinstead of a relationship silently breaks filter propagation. The report looks right for the developer and wrong for everyone else.- Inactive relationships don't propagate RLS. A model using
USERELATIONSHIPfor time intelligence can have a security hole hiding in the same structure. - Workspace admins and model owners can see everything regardless of role membership. Testing with an admin account proves nothing.
- Filtering a large fact table directly is the classic performance mistake. The filter is applied to every query, and a bad RLS design turns a fast model into a slow one for everyone.
- In embedded scenarios, a rule written as "if username is Worker then restrict, otherwise show everything" returns everything for a typo. Enumerate the expected values and fail closed.
- RLS doesn't apply to a downloaded .pbix or to someone with Build permission recreating the model. Distribution controls and RLS are separate problems.
Consultant notes
- Test with real accounts from each audience before go-live, not just View as. View as tests the DAX; only a real account tests the group membership, the workspace role and the app audience together.
- Insist on security groups rather than named users in role mappings. Named users become a maintenance burden and an audit finding.
- Measure the cost. Run Performance Analyzer with and without a role enforced and put the numbers in the design doc — it makes the "why is it slower than the demo" conversation short.
- Put the security table's ownership in writing. RLS that depends on a spreadsheet somebody updates when they remember is a control that will fail quietly.
- Where the client's real requirement is "they mustn't see that column", say clearly that RLS isn't the mechanism. Object-level security or a separate model is.
Sticky note: retest every role after any relationship change — RLS breaks silently and the person who notices is usually the wrong person