Key takeaways
- RLS filters rows in the semantic model, so it applies to every report built on that model.
- Static RLS uses fixed values per role; dynamic RLS filters on the signed-in user, usually with USERPRINCIPALNAME().
- With Power BI Embedded, the application passes the identity in the embed token, optionally with an extra value that you read with CUSTOMDATA().
- RLS only applies to viewers; workspace Members, Contributors and Admins see all data.
- A secure set-up has no fallback: anyone without a role or tag sees nothing.
How row-level security works in Power BI
Row-level security is a form of role-based access to data: a user's role determines which rows they see. In Power BI Desktop, you go to Modeling and choose Manage roles to create one or more roles. For each role, you write a DAX filter on a table, for example [Region] = "North". The filter is evaluated row by row; only rows for which the result is true remain visible.
The filter propagates through the relationships in your model to related tables. If you filter the Customer dimension, the orders and invoices of other customers are hidden as well. After publishing, you assign users or security groups to the roles in the Power BI service, in the security settings of the semantic model.
Because RLS lives in the model, it applies to every report and every analysis on that model, including when someone analyses the data in Excel. RLS restricts rows, not tables or columns. If you want to hide entire columns or tables, you need object-level security.
Static and dynamic RLS
With static RLS, the filter value is fixed in the role. You might create the roles North, East, South and West, for example, each with its own filter. With dynamic RLS, there is often just one role, and the filter depends on who is viewing.
A common set-up for dynamic RLS is an access table with an email address and a region or customer on each row. The role filter on that table is then [Email] = USERPRINCIPALNAME(). Pay attention to the direction of the relationship: the filter must be able to flow from the access table to your dimension. If it cannot, you can apply the security filter in both directions on the relationship, or write the filter directly on the dimension.
| Static RLS | Dynamic RLS | |
|---|---|---|
| Filter | Fixed value per role, such as [Region] = "North" | Depends on the user, via USERPRINCIPALNAME() or CUSTOMDATA() |
| Number of roles | One role per variant | Often one role for everyone |
| Management | A new region means a new role | A new user means a new row in the access table |
| Suitable for | A few stable groups | Many users or customers, changing access |
USERPRINCIPALNAME() and USERNAME()
USERPRINCIPALNAME() returns the user principal name (UPN) of the signed-in user, usually in the form name@company.com. In the Power BI service, USERNAME() returns the same value, but in Power BI Desktop it returns the form DOMAIN\user. So preferably use USERPRINCIPALNAME(), so that your filter works the same way in Desktop and in the service.
A UPN is not always the same as the email address in your access table, for example with aliases or after a name change. Check this before you go live. With Power BI Embedded in the "app owns data" scenario, both functions return the username that the application passes in the embed token.
RLS with Power BI Embedded: effective identity and CUSTOMDATA
In a customer portal, the end user does not sign in to Microsoft. The application signs in with a service principal, which itself is allowed to see all data. That is why the application passes an effective identity when it requests the embed token: a username, one or more roles and the semantic model. Power BI applies the RLS rules for that identity. If a model has RLS, Power BI will not issue an embed token without an identity.
Besides the username, you can pass a free-text value: customData. In a role filter, you read it with the DAX function CUSTOMDATA(), for example [CustomerCode] = CUSTOMDATA(). This is useful when your portal's users are not in your Microsoft Entra ID and you want to filter on an attribute that is managed in the portal, such as a customer number or branch. It also means the application is fully responsible for the correct value: a mistake in that mapping is a data breach.
In ENABLE, you link access profiles or a user tag to Power BI roles, optionally with CUSTOMDATA(). If a user has no tag, they get no access: ENABLE never falls back to unfiltered data. RLS is off by default and is switched on per customer.
Testing RLS before you go live
Always test row-level security with real user scenarios, not just with the role you have just created. In ENABLE, an administrator can also use "Preview as user" to check what a specific user gets to see. A practical order:
- In Power BI Desktop, use the View as option: choose a role and, for dynamic RLS, another user with a UPN from your access table.
- After publishing, test in the Power BI service through the security settings of the semantic model with Test as role, optionally as a specific user.
- Test the edge cases: a user without a row in the access table, a user with several regions, a new customer and an employee who has left.
- Check totals and cards. A measure with ALL() does not escape the RLS filter, but a table without a relationship to the filtered table is not filtered at all.
- In a portal, test the whole chain, from sign-in to embed token, and not just the model.
Common mistakes with row-level security
Most problems with RLS are not in the DAX formula, but in the set-up around it. Watch out for these pitfalls:
- Readers with the Member, Contributor or Admin role in the workspace. RLS does not apply to them; give readers the Viewer role or share through an app.
- Tables without a relationship to the filtered table. These are not filtered and therefore show all rows.
- No deliberate choice for users without a role. The secure starting point is: no role or tag means no data.
- An outdated access table. RLS is only as good as the mapping between users and data; leavers and changes must be kept up to date.
- Relying on hidden filters or slicers in the report. That is presentation, not security.
- Complex DAX in role filters on large fact tables. Preferably filter on small dimension tables; that is faster and easier to check.