Ever wondered who can actually see your data in Power BI? Spoiler alert—it’s not as private as you might think, unless you master Row-Level Security. In this episode, we don’t just check the RLS box. We break down the moving parts, from role definitions to the DAX expressions that quietly decide who sees what. Most people stop at setup—but we’re going to show you how every decision connects, so your data isn’t just locked—it’s architected for security.Why Row-Level Security Is a System, Not a CheckboxIf you’re used to locking down a sensitive spreadsheet by slapping a password on it, you already know there’s a gap between what you intend and what actually protects your data. Power BI with Row-Level Security can feel exactly the same. You hit the RLS toggle, assign a couple of roles, and think, “We’re covered.” But the reality is different: sensitive data finds a way out when those connections between roles, filters, and users aren’t carefully mapped. People rarely talk about the times when someone on the sales team opened up a dashboard and ended up with a clear view into numbers they never should have seen. But it happens. And it almost always starts with the assumption that RLS is just one more box to check during setup.Let’s put you in the shoes of someone who’s handed out dashboard access, thinking you’re just empowering a colleague. Feels harmless, right? But in too many organizations, access to one dashboard is access to everything behind the scenes. I’ve seen environments where a single “global” manager role accidentally allowed junior users to browse confidential HR data, just because the naming didn’t match reality. This isn’t a rare one-off, either—it’s closer to the rule than the exception, especially when a company is growing and dashboards are multiplying. Everyone wants to move fast, but when you move without structure, tiny holes get left behind that can turn into gaping gaps during an audit.On paper, RLS couldn’t sound simpler. Build a role—maybe call it “Sales”—add a filter like [Region] = ‘US’, and map users to it. But behind every dropdown and checkbox in Power BI, there’s a system making decisions about what data travels where. If you miss one relationship, or a filter doesn’t capture a new data source, it isn’t just a technical slip—it’s a security incident waiting to happen. You won’t know until someone stumbles over data they aren’t supposed to see—and sometimes, not even then. That’s what makes these little misses so hard to catch: they don’t come with warning bells, and standard audits often don’t drill deep enough to notice them.What gets overlooked is how tightly connected RLS components actually are. Most tutorials breeze through setup, walking you through role creation and filter basics. They’ll demonstrate mapping users and checking a preview box. But next to nobody pauses to interrogate how a filter on the Sales table affects, say, a related Territories table. Every bit of logic in RLS ripples into the others—assignments, DAX expressions, and even the naming of the roles themselves. Treat those choices as isolated steps, and you’re trusting the system not to have any loose ends. But one forgotten filter, and suddenly your careful role setup is like a sieve. This is one of those areas where the right analogy isn’t a locked door; it’s a mesh of threads. You change one part, the whole thing can subtly shift. Miss a thread, and you’ve created a leak that isn’t even obvious until it’s exploited.Actual incidents show how this plays out. Insider stories from analytics teams talk about the moment when the European sales lead stumbled onto data from North America’s pipeline. That wasn’t someone breaking in—it was a misapplied filter, an unintentional default, or a role that nobody thought to double-check. It’s not just real-world horror stories, either—Microsoft’s own vulnerability reports list misconfigured Row-Level Security as one of the top reasons for embarrassing, public data exposure in large organizations. For every news headline about a big breach, there are dozens that go unreported but have lasting consequences behind the scenes.What ties these problems together is the false sense of security RLS offers when you treat it like a simple item to cross off a to-do list. Studies from Microsoft’s security teams underscore that layering filters—stacking them across tables, applying additional DAX expressions, or using combinations of static and dynamic logic—ramps up both protection and complexity. Each layer you add doesn’t just make things safer. It multiplies the number of points where a configuration mistake can occur. Instead of a simple gatekeeper at the door, you’ve got a complex web where one missed node can undermine months of security planning.Let’s not glaze over the practical realities, either. A system might work fine in test, but drag it into production with real users, real departments, and ever-shifting teams, and you get situations where someone who changed teams last quarter still has access to the quarterly finance numbers. Names and assignments that made sense early on turn into mysterious legacy artifacts nobody wants to touch “just in case” they’re still being used. If you’re only toggling RLS at setup and moving on, you’re not protecting your data. You’re hoping nobody comes knocking at the wrong door—whether through curiosity or mistake.All this leads to one core truth: Row-Level Security is alive. It shifts with your organization. Every role you build, every DAX filter you apply, every group you assign—these aren’t small choices. They flow together to create a living, breathing system of protection, or, if ignored, a mesh full of holes. So before you move on to the next dashboard or handoff a model, pause and ask: am I actually managing a security system—or just hoping the switch is enough? The next part of this story starts with the piece at the heart of it all: the filter logic itself, and how it steers every other decision you make.Building a Secure Foundation: Data Filtering and Role DesignIf you’ve ever set up Row-Level Security thinking it was just about keeping certain rows out of view, you’re not alone. The main reason RLS gets messy is one simple misunderstanding—this isn’t just about what’s visible, it’s about shaping the entire user experience. One quick misstep and suddenly, that confidential bonus table or executive salary figure spills over into someone’s dashboard. People discover these leaks when reports are already in circulation, and by that point, backtracking can get awkward. The uncomfortable part? These breakdowns rarely look dramatic. It’s usually some edge case: a US manager logs in and sees just what they’re supposed to, but a London colleague opens the same model and, without anyone realizing it, finds New York’s numbers sitting right there.Think about kicking off a Fabric model for global sales. You want managers in different regions to see only their own data. The reflex is to drop in a filter, something simple: [Region] = “US” or [Country] = “UK.” But here’s the catch—how you write that filter, and how you hook it into your model, decides whether your system actually holds together or comes apart the minute things get more complex. Plenty of folks assume it’s dead simple, a one-line DAX filter, and then move on. But not all DAX behaves the same way. In fact, those nuances often break things later, especially when you move from Power BI Desktop over to Fabric, where cloud-level quirks start to surface.RLS gets tangled pretty quickly when you get into the weeds. People often bank on functions like USERNAME() or USERPRINCIPALNAME(), guessing they do the same thing or just grabbing whichever one a blog post uses. But swap USERNAME()—which pulls your Windows name in Desktop—for USERPRINCIPALNAME(), which grabs your email address in Fabric, and suddenly your filter logic starts to wobble. If you’ve ever had a test work in Desktop but collapse once you publish to Fabric, this is probably why. And picking wrong? That’s how gaps open up, quietly giving the wrong people a window into data they should never touch.Let’s get specific. For basic scenarios, a hardcoded check works: [Region] = “US” means only rows where the region is US show up. That’s textbook static RLS. It’s quick, it’s simple, and for fixed divisions—like a report meant only for US managers—it gets the job done. But as soon as someone asks for dynamic security, things shift. You might need to determine access based not just on the report viewer, but on relationships in another table—a user access table, for example, listing which users see which regions. Now your RLS filter isn’t just a fixed value; it’s a formula that looks up what regions each person should access in real time.Here’s where DAX can trip you up if you’re not careful. You might reach for something like USERPRINCIPALNAME() and then try to join it to your access table. But if your DAX relies on calculated columns or pulls in logic that doesn’t respond to the user context at view time, you can accidentally break the dynamic filter. Real-world example: a company sets up a dynamic filter relying on a calculated column in their fact table, only to realize later that calculated columns are evaluated when data is loaded, not when it’s viewed. This means users can’t see their personalized rows, or worse, see too much. You don’t notice until a user pings you asking why a report looks different for them than for someone else on the same team.Microsoft’s documentation and a long parade of MVP blog posts keep shouting from the rooftops: don’t trust calculated columns with RLS. Calculated columns are locked at refresh—they don’t change by user. For RLS to actually respond to the viewer, your filter logic needs to be built as a DAX expression on tables that update in the report context. Ignore this, and your dashboards quietly betray you, showing the same rows no matter who’s logged in.Static versus dynamic RLS isn’t just a
Become a supporter of this podcast: https://www.spreaker.com/podcast/m365-fm-modern-work-security-and-productivity-with-microsoft-365–6704921/support.
If this clashes with how you’ve seen it play out, I’m always curious. I use LinkedIn for the back-and-forth.