Row-Level Security in SQL Server: Closing the Gap Application Code Can Miss

Written by

in

Row-Level Security in SQL Server: Closing the Gap Application Code Can Miss

Lesson 1’s roles control access at the table level — “can this user query dbo.Order at all.” Row-Level Security (RLS) goes one level finer: which rows within a table a given user can see, enforced by the engine itself rather than trusted entirely to application-layer WHERE clauses. For multi-tenant applications, this closes a genuinely common, genuinely serious real-world bug class.

RLS: the invisible WHERE clause(a gate every query passes through)WestEastWestNorthSecurityPredicate@region = USER_NAME()runs on EVERY queryWest rows pass ✓others never appear ✗no WHERE clause in the calling query — the filter is invisible and automaticenforced by the engine,not remembered by developersstill keep app-level checks —RLS guards rows, not“can this user act at all” 📌

A Basic Security Predicate

CREATE FUNCTION dbo.fn_SecurityPredicate (@region NVARCHAR(50))
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN SELECT 1 AS result WHERE @region = USER_NAME() OR IS_MEMBER('db_owner') = 1;

CREATE SECURITY POLICY OrderRegionPolicy
ADD FILTER PREDICATE dbo.fn_SecurityPredicate(region) ON dbo.SomeTable
WITH (STATE = ON);

Once this policy is active, SELECT * FROM dbo.SomeTable run by a non-admin user automatically only returns rows matching their region — with no WHERE clause required in the calling code at all. The filter applies transparently to every query against the table, including ones written by developers who don’t even know RLS exists on it.

Why This Matters in Practice

Closes the gap where a forgotten WHERE clause in a new report could leak another tenant’s data

This is the exact same category of reasoning as constraints vs. application-only validation (Chapter 5): trusting every single query, in every report, written by every developer who ever touches the codebase, to correctly filter by tenant is a much weaker guarantee than the engine enforcing it structurally. One forgotten WHERE tenant_id = @currentTenant in a hastily-written admin report is a real, documented category of data breach — RLS makes that specific mistake impossible rather than merely unlikely.

Auditing Existing Permissions

SELECT dp.name AS principal_name, dp.type_desc, o.permission_name, o.state_desc
FROM sys.database_permissions o
JOIN sys.database_principals dp ON dp.principal_id = o.grantee_principal_id
WHERE o.major_id = OBJECT_ID('dbo.OrderLog');

A quick, worthwhile habit before granting anything new: check what’s already granted on a sensitive table, so you’re not stacking overlapping permissions you can’t easily reason about later. Combined with Lesson 1’s DENY-always-wins rule, a table can accumulate a genuinely confusing tangle of GRANTs and DENYs across multiple roles over time — this query is how you actually see the full picture before adding to it.

Common mistake: Treating RLS as a replacement for application-level authorization checks entirely. RLS is a defense-in-depth layer (the same philosophy from Chapter 5’s CHECK constraints) — keep sensible application-level filtering too, since RLS protects the data layer specifically, not business logic like “can this user perform this action at all,” which is a broader question than row visibility.
Practice tip: Build the security policy above against a small table with a handful of rows across two different “regions,” then query it as a non-admin user and confirm rows from the other region genuinely don’t appear — not filtered out by your query, but invisible at the engine level, confirmed by trying to explicitly SELECT a row you know exists in the other region and getting zero rows back.

Enjoyed this?

Subscribe to get every new SQL Server lesson as soon as it’s published, and share it with a developer who’d find it useful.

📡 Subscribe via RSS  | 
Share on X  | 
Share on LinkedIn  | 
Share on Facebook

Want the full structured course with quizzes, projects, and 10+ exercises per chapter? Check out SQL Server for Developers & DBAs, coming soon on this site.