The ultimate Fabric, Power BI, SQL, and AI community-led learning event. Save €200 with code FABCOMM.
Get registeredCompete to become Power BI Data Viz World Champion! First round ends August 18th. Get started.
Hi Community,
Is there a way to mask some column values based on who is connecting to the model?
Let's take as an example such table:
I have different group permission: PBI-Admin and PBI-NonUS set up in PoweBI service. What I want to achive is:
1. if user is member of PBI-Admin group, he/she can see everything without changes,
2. if user is a member of PBI-NonUS group and the case is from USA, then I want to mask "Text Field" value (display "MASKED").
It's very essential, I only want to cover a single cell. All users should see the case number regardless of country.
The case involves my large PBI model, from which many reports have been created. Moreover, the model is published and users can create their own analysis.
The issue does not apply to RLS (the whole row disappears) or OLS (the whole column disappears).
I will also need to implement this in the reports created in report builder (source: Azure SQL db).
I look forward to your ideas or experiences!
Solved! Go to Solution.
Dear @Klaudia_00,
You're absolutely right — RLS hides entire rows, and OLS hides entire columns, which doesn't help when you need cell-level masking (i.e., only mask specific values in a column based on conditions).
Here's how to achieve cell-level data masking in Power BI based on user groups like PBI-Admin and PBI-NonUS:
Goal:
Solution Overview (Power BI)
1. Create a Security Table
Create a table (can be manually created in Power BI or from Azure SQL) that maps usernames to groups:
UserName | Group |
user1@domain.com | PBI-Admin |
user2@domain.com | PBI-NonUS |
2. Create a User-Based Masking Logic Measure/Column
Assuming your table is named Cases and you have UsernameGroupMapping with a relationship on the current user:
A. Create a DAX measure for the masked text field:
Masked Text Field =
VAR CurrentUser = USERPRINCIPALNAME()
VAR UserGroup =
CALCULATE(
MAX('UsernameGroupMapping'[Group]),
'UsernameGroupMapping'[UserName] = CurrentUser
)
RETURN
IF(
UserGroup = "PBI-NonUS" && Cases[Country] = "USA",
"MASKED",
Cases[Text Field]
)
Use this measure in your visual instead of the original Text Field.
3. (Optional) Create a Calculated Column for Use in Paginated Reports
Since Paginated Reports may need queryable data (not just measures), create a calculated column with similar logic (but note: this exposes logic in the model):
MaskedText =
VAR CurrentUser = USERPRINCIPALNAME()
VAR UserGroup =
LOOKUPVALUE(
'UsernameGroupMapping'[Group],
'UsernameGroupMapping'[UserName], CurrentUser
)
RETURN
IF(
UserGroup = "PBI-NonUS" && Cases[Country] = "USA",
"MASKED",
Cases[Text Field]
)
For Paginated Reports from Azure SQL, use a similar CASE logic inside a SQL view or function, driven by the user login via SUSER_SNAME() or Power BI passed parameter.
For Azure SQL DB (Paginated Reports)
Since Power BI passes user context differently, you'd need to:
SELECT
CaseID,
Country,
CASE
WHEN @UserGroup = 'PBI-NonUS' AND Country = 'USA' THEN 'MASKED'
ELSE [Text Field]
END AS [Text Field]
FROM Cases
Please mark this post as solution if it helps you. Appreciate Kudos.
Hi @Klaudia_00 , Thank you for reaching out to the Microsoft Community Forum.
Please try below:
CREATE VIEW vw_Cases_Masked
AS
SELECT
CaseID,
Country,
CASE
WHEN Country = 'USA' AND EXISTS (
SELECT 1 FROM UsernameGroupMapping
WHERE [Group] = 'PBI-NonUS' AND Username = SESSION_CONTEXT(N'UserName')
)
THEN 'MASKED'
ELSE [Text Field]
END AS [Text Field]
FROM Cases;
VAR CurrentUser = USERPRINCIPALNAME()
VAR UserGroup =
CALCULATE(
MAX('UsernameGroupMapping'[Group]),
'UsernameGroupMapping'[Username] = CurrentUser
)
RETURN
IF(
UserGroup = "PBI-NonUS" && Cases[Country] = "USA",
"MASKED",
Cases[Text Field]
)
Source = Sql.Database("your_server_name", "your_database_name"),
Cases = Source{[Schema="dbo",Item="vw_Cases_Masked"]}[Data]
in
Cases
If this helped solve the issue, please consider marking it 'Accept as Solution' so others with similar queries may find it more easily. If not, please share the details, always happy to help.
Thank you.
Hi @Klaudia_00 , Please let us know if your issue is solved. If it is, consider marking the answer that helped 'Accept as Solution', so others with similar queries can find it easily. If not, please share the details.
Thank you.
Hi @Klaudia_00 , Please let us know if your issue is solved. If it is, consider marking the answer that helped 'Accept as Solution', so others with similar queries can find it easily. If not, please share the details.
Thank you.
Hi @Klaudia_00 , Please let us know if your issue is solved. If it is, consider marking the answer that helped 'Accept as Solution', so others with similar queries can find it easily. If not, please share the details.
Thank you.
Hi @Klaudia_00 , Thank you for reaching out to the Microsoft Community Forum.
Please try below:
CREATE VIEW vw_Cases_Masked
AS
SELECT
CaseID,
Country,
CASE
WHEN Country = 'USA' AND EXISTS (
SELECT 1 FROM UsernameGroupMapping
WHERE [Group] = 'PBI-NonUS' AND Username = SESSION_CONTEXT(N'UserName')
)
THEN 'MASKED'
ELSE [Text Field]
END AS [Text Field]
FROM Cases;
VAR CurrentUser = USERPRINCIPALNAME()
VAR UserGroup =
CALCULATE(
MAX('UsernameGroupMapping'[Group]),
'UsernameGroupMapping'[Username] = CurrentUser
)
RETURN
IF(
UserGroup = "PBI-NonUS" && Cases[Country] = "USA",
"MASKED",
Cases[Text Field]
)
Source = Sql.Database("your_server_name", "your_database_name"),
Cases = Source{[Schema="dbo",Item="vw_Cases_Masked"]}[Data]
in
Cases
If this helped solve the issue, please consider marking it 'Accept as Solution' so others with similar queries may find it more easily. If not, please share the details, always happy to help.
Thank you.
Thanks for the reply, I will test it on my end focusing firstly on PBI desktop. Unfortunately, I'm afraid it will affect performance a lot.
I was wondering if there is another way, such as shared expressions in tabular editor and implementation of this in M language (where you connect to a specific view on the database)?
Dear @Klaudia_00,
You're absolutely right — RLS hides entire rows, and OLS hides entire columns, which doesn't help when you need cell-level masking (i.e., only mask specific values in a column based on conditions).
Here's how to achieve cell-level data masking in Power BI based on user groups like PBI-Admin and PBI-NonUS:
Goal:
Solution Overview (Power BI)
1. Create a Security Table
Create a table (can be manually created in Power BI or from Azure SQL) that maps usernames to groups:
UserName | Group |
user1@domain.com | PBI-Admin |
user2@domain.com | PBI-NonUS |
2. Create a User-Based Masking Logic Measure/Column
Assuming your table is named Cases and you have UsernameGroupMapping with a relationship on the current user:
A. Create a DAX measure for the masked text field:
Masked Text Field =
VAR CurrentUser = USERPRINCIPALNAME()
VAR UserGroup =
CALCULATE(
MAX('UsernameGroupMapping'[Group]),
'UsernameGroupMapping'[UserName] = CurrentUser
)
RETURN
IF(
UserGroup = "PBI-NonUS" && Cases[Country] = "USA",
"MASKED",
Cases[Text Field]
)
Use this measure in your visual instead of the original Text Field.
3. (Optional) Create a Calculated Column for Use in Paginated Reports
Since Paginated Reports may need queryable data (not just measures), create a calculated column with similar logic (but note: this exposes logic in the model):
MaskedText =
VAR CurrentUser = USERPRINCIPALNAME()
VAR UserGroup =
LOOKUPVALUE(
'UsernameGroupMapping'[Group],
'UsernameGroupMapping'[UserName], CurrentUser
)
RETURN
IF(
UserGroup = "PBI-NonUS" && Cases[Country] = "USA",
"MASKED",
Cases[Text Field]
)
For Paginated Reports from Azure SQL, use a similar CASE logic inside a SQL view or function, driven by the user login via SUSER_SNAME() or Power BI passed parameter.
For Azure SQL DB (Paginated Reports)
Since Power BI passes user context differently, you'd need to:
SELECT
CaseID,
Country,
CASE
WHEN @UserGroup = 'PBI-NonUS' AND Country = 'USA' THEN 'MASKED'
ELSE [Text Field]
END AS [Text Field]
FROM Cases
Please mark this post as solution if it helps you. Appreciate Kudos.