Row-Level Security (RLS) in SQL Server enables fine-grained control over data access, ensuring users can only view or modify rows pertinent to their roles or responsibilities. In this blog post, we will go over a step-by-step guide to implementing RLS, from table creation to cleanup, with detailed explanations for each step.
Imagine a library where each librarian is responsible for specific sections. To maintain order, librarians should only access books in their assigned areas. Similarly, in databases, it’s crucial to ensure users can only access data relevant to their roles.
1. Create a Sample Table
We’ll create a Sales table to store sales data, including the salesperson’s name, country, and sales amount.
CREATE TABLE Sales ( SaleID INT IDENTITY(1,1) PRIMARY KEY, SalesPerson NVARCHAR(50), Country NVARCHAR(50), SalesAmount DECIMAL(10, 2) );
2. Insert Sample Data
Populate the Sales table with sample records.
INSERT INTO Sales (SalesPerson, Country, SalesAmount) VALUES ('Alice', 'USA', 1000.00), ('Bob', 'USA', 1500.00), ('Charlie', 'Canada', 2000.00), ('Alice', 'Canada', 2500.00), ('Bob', 'Mexico', 3000.00);
Select the data from the table.
SELECT * FROM Sales;
Here is the resultset:
3. Create Users
Define database users corresponding to the salespersons.
CREATE USER Alice WITHOUT LOGIN; CREATE USER Bob WITHOUT LOGIN; CREATE USER Charlie WITHOUT LOGIN;
4. Grant Select Permission
Allow the users to select data from the Sales table.
GRANT SELECT ON Sales TO Alice; GRANT SELECT ON Sales TO Bob; GRANT SELECT ON Sales TO Charlie;
5. Create a Security Predicate Function
Develop a function that filters rows based on the current user. This function, known as a security predicate, defines the logic that determines which rows a user can access. By checking if the SalesPerson column matches the current user’s name, we ensure that users can only access their own sales records.
CREATE FUNCTION dbo.fn_SalesSecurityPredicate(@SalesPerson AS sysname) RETURNS TABLE WITH SCHEMABINDING AS RETURN SELECT 1 AS fn_SalesSecurityPredicate_Result WHERE @SalesPerson = USER_NAME();
6. Create a Security Policy
Apply the security predicate to the Sales table using a security policy. A security policy binds the predicate function to the table, enforcing the row-level filter whenever the table is accessed. This centralizes the access control logic within the database, ensuring consistent enforcement across all queries.
CREATE SECURITY POLICY SalesSecurityPolicy ADD FILTER PREDICATE dbo.fn_SalesSecurityPredicate(SalesPerson) ON dbo.Sales WITH (STATE = ON);
7. Test Row-Level Security
Verify that each user can only access their respective sales records.
As Alice:
EXECUTE AS USER = 'Alice'; SELECT * FROM Sales; REVERT;
Expected Result:
As Bob:
EXECUTE AS USER = 'Bob'; SELECT * FROM Sales; REVERT;
Expected Result:
As Charlie:
EXECUTE AS USER = 'Charlie'; SELECT * FROM Sales; REVERT;
Expected Result:
8. Cleanup
Remove the security policy, function, users, and table to clean up the database.
DROP SECURITY POLICY SalesSecurityPolicy; DROP FUNCTION dbo.fn_SalesSecurityPredicate; DROP USER Alice; DROP USER Bob; DROP USER Charlie; DROP TABLE Sales;
Summary
In this blog post, we implemented Row-Level Security in SQL Server to restrict data access based on the user’s identity. By creating a security predicate function and applying it through a security policy, we ensured that each salesperson could only view their own sales records. This approach centralizes access control within the database, providing a robust and maintainable security solution.
You can connect with me on LinkedIn.
Reference: Pinal Dave (https://blog.sqlauthority.com)
First appeared on SQL SERVER – Implementing Row-Level Security (RLS)