Row level security: praktisch stappenplan voor IT en data
Row level security: praktisch stappenplan voor IT en data

Beveiliging op rijniveau (row level security) is de meest betrouwbare manier om gebruikers alleen de rijen te laten zien die voor hen bestemd zijn. De aanbeveling is helder: dwing dit af op databaseniveau, niet alleen in rapporten. Centrale policies gelden dan voor alle applicaties tegelijk, er is een enkele bron van waarheid, en een vergeten WHERE-clausule in een applicatie legt geen data bloot.
Kies voor database-eerst wanneer:
- meerdere applicaties of rapporten dezelfde database gebruiken
- je een centrale plek wilt voor beheer en audit
- je AVG-compliance wilt aantonen met aantoonbare toegangscontrole
- je werkt met gevoelige gegevens zoals persoonsgegevens of financiële data
Kies voor rapport-eerst (Power BI) wanneer:
- je snel een prototype wilt bouwen zonder database-aanpassingen
- de data uitsluitend via Power BI wordt ontsloten
- je beperkte IT-ondersteuning hebt en de beheerder alleen Power BI beheert
Inhoudsopgave
- Wat is row level security en hoe werkt het?
- Hoe implementeer je row level security in Power BI?
- Hoe implementeer je dit in PostgreSQL en SQL Server?
- Database-level versus rapport-level: wanneer kies je wat?
- Welke valkuilen moet je kennen?
- Hoe test en valideer je de toegangsregels?
- Welke best practices gelden voor ontwerp en beheer?
- Wat kost de uitrol en hoe lang duurt het?
- Herkenbare use-cases met patroonvoorbeelden
- Waarom database-eerst vaak de beste keuze is
- Belangrijkste inzichten
- Wat werkt in de praktijk
- Aanbevolen bronnen voor verdere verdieping
- Veelgestelde vragen
Wat is row level security en hoe werkt het?
Beveiliging op rijniveau filtert welke rijen een gebruiker ziet op basis van wie die gebruiker is. Niet op tabelniveau, niet op kolomniveau, maar per rij. Een salesmanager in Amsterdam ziet alleen de deals uit zijn regio; zijn collega in Berlijn ziet de hare. Hetzelfde rapport, hetzelfde model, andere data.

Technisch werkt het als een extra WHERE-clausule die automatisch bij elke query wordt toegevoegd. De database of het rapportagemodel evalueert een policy of filterregel, vergelijkt die met de identiteit van de gebruiker, en geeft alleen de rijen terug die aan de voorwaarde voldoen. De gebruiker ziet het resultaat, niet de filter zelf.
Hoe verschilt dit van kolommaskering en tabeltoegang?
| Techniek | Wat het afdekt | Wat het niet afdekt |
|---|---|---|
| Beveiliging op rijniveau | Welke rijen zichtbaar zijn | Kolommen, aggregaties, metadata |
| Kolommaskering | Welke kolommen zichtbaar zijn | Rijen, aggregaties |
| Tabeltoegang (rechten) | Toegang tot de hele tabel | Fijnmazige rij- of kolomselectie |
De drie technieken vullen elkaar aan. Beveiliging op rijniveau alleen verbergt geen kolommen. Kolommaskering alleen verbergt geen rijen. Wie echt gevoelige data beschermt, combineert beide.
Typische kolommen waarop je filtert:
- tenant_id of organisatie_id: voor multi-tenant omgevingen waarbij elke organisatie alleen zijn eigen data ziet
- regio of vestiging: voor salesteams of regionale rapportages
- medewerker_id: voor HR-data waarbij elke medewerker alleen zijn eigen gegevens inziet
Hoe implementeer je row level security in Power BI?
Beveiliging op rijniveau in Power BI werkt in twee stappen: definieer rollen en filters in Power BI Desktop, en beheer rollidmaatschap na publicatie in de Power BI-service.
Stap voor stap
- Open Power BI Desktop en ga naar het tabblad Modellering.
- Klik op Rollen beheren en maak een nieuwe rol aan, bijvoorbeeld “Regio Noord”.
- Definieer een DAX-filterexpressie op de relevante tabel.
- Publiceer het rapport naar de Power BI-service.
- Wijs gebruikers of beveiligingsgroepen toe aan de rol via Beveiliging in de dataset-instellingen.
- Test de rol met de functie Weergeven als in de service.
Statische versus dynamische filters
Een statische filter is eenvoudig en snel:
[Regio] = "Noord"
Elke gebruiker in de rol “Regio Noord” ziet alleen rijen waar de kolom Regio gelijk is aan “Noord”. Handig voor kleine teams met vaste regio’s, maar niet schaalbaar als je tientallen regio’s hebt.
Een dynamische filter gebruikt de identiteit van de ingelogde gebruiker:
[E-mailadres] = USERPRINCIPALNAME()
Dit vergelijkt de e-mail van de gebruiker met een kolom in de data. Werkt goed wanneer elke gebruiker een eigen rij heeft in een toewijzingstabel. Een uitgebreidere variant met een toewijzingstabel:
[RegioKey] IN
CALCULATETABLE(
VALUES(Toewijzing[RegioKey]),
Toewijzing[Email] = USERPRINCIPALNAME()
)
Hiermee kan een gebruiker meerdere regio’s toegewezen krijgen zonder dat je de rol aanpast.
Dynamische filters via USERPRINCIPALNAME() zijn de aanbevolen aanpak voor productieomgevingen. Statische filters vereisen handmatige aanpassingen bij elke organisatiewijziging; dynamische filters schalen mee met je gebruikersbestand zonder dat je het model opnieuw hoeft te publiceren.
Pro-tip: Controleer altijd of het e-mailadres in de toewijzingstabel exact overeenkomt met het UPN in Azure Active Directory. Een hoofdletterverschil of een alias in plaats van het primaire adres is de meest voorkomende reden waarom een gebruiker plotseling niets meer ziet.
Beheer en valkuilen
Gebruikers met de rol Beheerder, Lid of Bijdrager in een Power BI-werkruimte omzeilen de beveiliging op rijniveau standaard. Alleen gebruikers met de rol Kijker worden gefilterd. Zorg dus dat interne beheerders niet per ongeluk als Kijker worden ingesteld voor testdoeleinden, en dat productiegebruikers niet als Beheerder zijn toegevoegd.
Hoe implementeer je dit in PostgreSQL en SQL Server?
PostgreSQL
Na het inschakelen van beveiliging op rijniveau op een tabel geldt standaard een deny-all: niemand ziet rijen totdat er een policy bestaat. Communiceer dit vooraf aan je team om verwarring te voorkomen.
-- Stap 1: schakel in op tabelniveau
ALTER TABLE orders ENABLE ROW LEVEL SECURITY;
-- Stap 2: maak een policy aan
CREATE POLICY tenant_isolatie ON orders
USING (tenant_id = current_setting('app.current_tenant')::uuid)
WITH CHECK (tenant_id = current_setting('app.current_tenant')::uuid);
-- Stap 3: geef de applicatierol leesrechten
GRANT SELECT ON orders TO app_user;
De USING-clausule bepaalt welke rijen zichtbaar zijn bij SELECT. De WITH CHECK-clausule valideert of nieuwe of gewijzigde rijen aan de policy voldoen bij INSERT en UPDATE. PostgreSQL evalueert policies vóór andere query-voorwaarden; meerdere permissieve policies combineren met OR.
Superusers en rollen met BYPASSRLS omzeilen alle policies. Gebruik dit privilege uitsluitend voor beheeraccounts en documenteer wie het heeft.
Let op referentiële integriteit: primary keys en foreign key-controles omzeilen de RLS-controles in PostgreSQL. Dit kan een indirecte informatielek vormen wanneer een gebruiker via een foutmelding kan afleiden dat een rij bestaat die hij niet mag zien.
SQL Server
In SQL Server werkt beveiliging op rijniveau via inline table-valued functions en security policies:
-- Stap 1: maak een filterfunctie
CREATE FUNCTION dbo.fn_rls_filter(@RegioBeheerder AS sysname)
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN
SELECT 1 AS resultaat
WHERE @RegioBeheerder = USER_NAME()
OR IS_ROLEMEMBER('db_owner') = 1;
-- Stap 2: koppel de functie aan een security policy
CREATE SECURITY POLICY RegioBeleid
ADD FILTER PREDICATE dbo.fn_rls_filter(RegioBeheerder)
ON dbo.SalesOrders
WITH (STATE = ON);
De stappen zijn: rechten geven, een inline filterfunctie aanmaken, het predicate binden aan een security policy, en testen onder verschillende gebruikersprincipals.
Prestatietip: indexeer altijd de kolom die in de policy wordt gebruikt, zoals tenant_id of RegioBeheerder. Een policy op een niet-geïndexeerde kolom leidt tot full table scans en merkbare vertraging naarmate de tabel groeit.
Database-level versus rapport-level: wanneer kies je wat?
| Criterium | Database-level | Rapport-level (Power BI) |
|---|---|---|
| Waar afdwingen | In de database, voor alle apps | Alleen in het rapport of de dataset |
| Beheerbaarheid | Centrale policies, een bron van waarheid | Per rapport of dataset beheren |
| Breedte van dekking | Alle applicaties die de database gebruiken | Alleen Power BI-rapporten |
| Performantie | Afhankelijk van indexering en predicaatcomplexiteit | Afhankelijk van DAX-complexiteit en modelgrootte |
| Testbaarheid | SQL-queries, EXPLAIN ANALYZE, sessie-emulatie | “Weergeven als”-functie in Power BI-service |
| Compliance en audit | Logging op databaseniveau, aantoonbaar voor AVG | Beperkt tot Power BI-auditlogs |

Centrale database-policies voorkomen dat een vergeten WHERE-clausule in een applicatie data blootstelt. Dat is het kernargument voor database-eerst.
Rapport-level beveiliging is zinvol wanneer de data uitsluitend via Power BI wordt ontsloten en je snel wilt starten zonder database-aanpassingen. Voor kleine organisaties met beperkte IT-ondersteuning is dit vaak de snelste route naar een werkende oplossing.
Beslisregel voor kleine organisaties: als je data via meer dan een applicatie wordt ontsloten, of als je een AVG-audit verwacht, kies dan voor database-level. Als Power BI de enige toegangspoort is en je team klein is, begin dan met rapport-level en migreer later.
Welke valkuilen moet je kennen?
De meest voorkomende fout is het activeren van beveiliging op rijniveau zonder policies aan te maken. Het resultaat: niemand ziet nog data, en de helpdesk krijgt meldingen. Test altijd in een afgeschermde omgeving voordat je dit in productie inschakelt.
Andere valkuilen:
- Complexe predicaten met joins: eenvoudige WHERE-clausules presteren veel beter dan predicaten die joins naar andere tabellen gebruiken. Een policy die een subquery uitvoert bij elke rij schaalt slecht.
- Verkeerde sleutelkolommen: gebruik stabiele, betrouwbare identifiers zoals een UUID of een numeriek ID. Gebruik nooit een naam of e-mailadres als sleutelkolom; die veranderen.
- Administratieve bypass:
BYPASSRLSin PostgreSQL endb_ownerin SQL Server omzeilen alle policies. Documenteer wie deze rechten heeft en beperk ze tot het minimum. - Backups en exports: sommige backup-tools exporteren data als superuser en omzeilen daarmee de policies. Controleer of je backup-strategie rekening houdt met toegangscontrole.
- Referentiële integriteit als zijkanaal: in PostgreSQL kunnen foreign key-controles indirect onthullen dat een rij bestaat die een gebruiker niet mag zien.
Mitigaties: houd predicaten eenvoudig, indexeer sleutelkolommen, documenteer alle bypass-rechten, en test na elke schema-wijziging opnieuw.
Hoe test en valideer je de toegangsregels?
Testen is geen optionele stap. Een policy die er goed uitziet maar verkeerd is geconfigureerd, geeft een vals gevoel van veiligheid.
Testchecklist
- Maak testaccounts aan voor elke rol die je wilt valideren.
- Log in als testgebruiker en controleer welke rijen zichtbaar zijn.
- Probeer een rij in te voegen of bij te werken die buiten de policy valt en verifieer dat dit wordt geweigerd.
- Controleer het queryplan met
EXPLAIN ANALYZE(PostgreSQL) om te zien of de index wordt gebruikt. - Log afwijzingen en controleer of de auditlogs de verwachte entries bevatten.
- Test na elke schema-wijziging opnieuw.
Voorbeeldtestcases
- Salesmanager regio Noord: logt in, ziet alleen orders met
regio = 'Noord', ziet geen orders uit andere regio’s. - Gebruiker met meerdere regio’s: logt in, ziet orders uit alle toegewezen regio’s via de toewijzingstabel.
- Beheerder met BYPASS-rechten: logt in, ziet alle rijen. Verifieer dat dit bewust is en gedocumenteerd.
Sessie-emulatie in PostgreSQL
-- Stel de huidige tenant in voor de sessie
SET app.current_tenant = '3f2a1b00-...';
-- Voer een testquery uit als de applicatierol
SET ROLE app_user;
SELECT * FROM orders;
-- Verwacht: alleen rijen met tenant_id = '3f2a1b00-...'
In Power BI gebruik je de “Weergeven als”-functie in de Power BI-service om een specifieke gebruiker of rol te emuleren. Zo zie je exact wat die gebruiker ziet zonder in te loggen met zijn account. Gebruik hiervoor een testaccount met een bekend e-mailadres dat overeenkomt met een rij in je toewijzingstabel.
Welke best practices gelden voor ontwerp en beheer?
Least privilege als uitgangspunt. Ontwerp policies met zo min mogelijk privileges per rol. Geef een gebruiker alleen toegang tot de rijen die hij echt nodig heeft, niet tot een bredere set “voor het geval dat”.
Verdere aanbevelingen:
- Indexeer sleutelkolommen: elke kolom die in een policy of DAX-filter wordt gebruikt, krijgt een index. Zonder index schaalt de oplossing niet.
- Combineer met kolommaskering en audit logging: beveiliging op rijniveau is een laag binnen een gelaagde beveiligingsstrategie. Kolommaskering verbergt gevoelige velden; audit logging maakt toegang aantoonbaar.
- Documenteer alle policies en bypass-rechten: sla dit op in versiecontrole samen met de schemawijzigingen.
- Gebruik geautomatiseerde tests: voeg RLS-tests toe aan je CI/CD-pipeline zodat een schema-wijziging niet ongemerkt een policy breekt.
- Beveiligde identiteiten en MFA: combineer encryptie, fijnmazige toegangscontrole en continue monitoring. Multi-factor authenticatie (MFA, waarbij een gebruiker naast een wachtwoord ook een tweede verificatiestap doorloopt) verkleint het risico van gecompromitteerde accounts.
- Single source of truth: beheer policies op een centrale plek. Verspreid beheer over meerdere tools of rapporten leidt tot inconsistenties.
Wat kost de uitrol en hoe lang duurt het?
Licentiechecks
Controleer voor Power BI of de gebruikte licenties het delen van beveiligde rapporten ondersteunen. Beveiliging op rijniveau werkt in Power BI Pro en Premium; met een gratis licentie kun je rollen definiëren maar niet publiceren naar andere gebruikers. Controleer ook welke databaserollen BYPASSRLS hebben en of dat bewust is.
Rolloutstappen en tijdlijn
| Fase | Duur | Activiteiten |
|---|---|---|
| Proof of concept | een korte periode | Ontwerp policies, bouw testomgeving, valideer met testaccounts |
| Pilot | enkele weken | Uitrol naar een beperkte gebruikersgroep, verzamel feedback, pas policies aan |
| Productie | Afhankelijk van aantal datasets en tabellen | Volledige uitrol, monitoring, documentatie, overdracht aan beheer |
Betrokken stakeholders
- Beheerder: beheert rollen, lidmaatschappen en bypass-rechten
- Data-engineer: ontwerpt en implementeert policies en indexen
- Business owner: definieert wie welke data mag zien en valideert de testresultaten
De grootste tijdsinvestering zit niet in de technische implementatie, maar in het ophalen van de toegangsvereisten bij de business. Wie mag wat zien? Dat gesprek duurt langer dan het schrijven van de policies.
Herkenbare use-cases met patroonvoorbeelden
Sales per regio
Het meest voorkomende patroon. Elke salesmedewerker ziet alleen de orders uit zijn regio.
- Power BI:
[RegioKey] = LOOKUPVALUE(Medewerker[RegioKey], Medewerker[Email], USERPRINCIPALNAME()) - SQL:
WHERE regio_id = current_setting('app.regio_id')::int
Voeg een toewijzingstabel toe als medewerkers meerdere regio’s beheren.
Organisatie-isolatie
In een omgeving waar meerdere organisaties dezelfde database delen, isoleert een organisatie_id-kolom de data per tenant.
- Sla de organisatie-identifier op in de sessiecontext bij het inloggen.
- Elke policy vergelijkt de rij-kolom met de sessiewaarde.
- Controleer dat de applicatielaag de juiste identifier instelt; een fout hier lekt data naar de verkeerde organisatie.
HR-gegevens en AVG
Persoonsgegevens zoals salarisschalen, beoordelingen en ziekteverzuim vallen onder de AVG (Algemene Verordening Gegevensbescherming). Beperk zichtbaarheid op rijniveau tot de betrokken medewerker en zijn directe leidinggevende, en combineer dit met kolommaskering voor velden zoals BSN of bankrekeningnummer.
- Documenteer de toegangsregels als onderdeel van je verwerkingsregister.
- Log wie welke HR-rijen heeft ingezien en wanneer.
- Laat de policies periodiek toetsen door een privacy-adviseur of IT-partner.
Waarom database-eerst vaak de beste keuze is
Centrale policies verminderen de beheerlast en beperken de kans op lekken door fouten in applicatiecode. Een applicatie die een WHERE-clausule vergeet, lekt geen data als de database zelf de toegang afdwingt. Dat is een fundamenteel verschil met rapport-level beveiliging, waar elke nieuwe tool of elk nieuw rapport opnieuw moet worden geconfigureerd.
Voor AVG-compliance is dit argument extra sterk. Een verwerkingsregister en een audit trail zijn makkelijker te onderbouwen wanneer de toegangscontrole op een centrale plek zit. Logging op databaseniveau registreert elke query, niet alleen de queries via een specifiek rapport.
Rapport-level beveiliging volstaat wanneer Power BI de enige toegangspoort is en de organisatie klein is. Maar zodra een tweede applicatie de data ontsluit, of zodra een externe audit de toegangscontrole toetst, is database-eerst de veiligere keuze.
De aanbeveling: begin met rapport-level als je snel wilt starten, maar plan de migratie naar database-level in zodra de scope groeit. Wacht niet tot een incident je dwingt.
Belangrijkste inzichten
Beveiliging op rijniveau is het meest betrouwbaar wanneer het centraal in de database wordt afgedwongen, gecombineerd met kolommaskering, audit logging en MFA.
| Punt | Details |
|---|---|
| Database-eerst voor centraal beheer | Policies in de database gelden voor alle applicaties en voorkomen lekken door applicatiefouten. |
| Test altijd voor productie | Activeer beveiliging op rijniveau nooit zonder policies; het deny-all effect blokkeert alle gebruikers direct. |
| Indexeer sleutelkolommen | Een policy op een niet-geïndexeerde kolom leidt tot full table scans en merkbare vertraging. |
| Combineer met masking en logging | Beveiliging op rijniveau alleen verbergt geen kolommen; voeg kolommaskering en audit logging toe voor volledige dekking. |
| Plan de uitrol in fasen: proof of concept in een korte periode, gevolgd door een pilot van enkele weken, daarna productie-uitrol met monitoring en documentatie. |
Wat werkt in de praktijk
De technische implementatie van beveiliging op rijniveau is zelden het moeilijkste deel. De policies schrijven, de indexen aanmaken, de DAX-filters definiëren: dat is een kwestie van uren tot dagen. Wat organisaties structureel onderschat, is het voorwerk.
Wie mag welke rijen zien? Dat klinkt als een simpele vraag, maar in de praktijk blijkt het antwoord verspreid over drie afdelingen, twee spreadsheets en een ongeschreven afspraak uit 2019. Zonder een helder toegangsmodel schrijf je policies die technisch correct zijn maar zakelijk verkeerd. En dan ontdek je dat pas als een gebruiker belt omdat hij data ziet die niet voor hem bestemd is.
Mijn advies: begin met het toegangsmodel, niet met de code. Zet de regels op papier, laat ze valideren door de business owner, en gebruik dat document als basis voor je policies. Versiecontrole voor policies is geen luxe; het is de enige manier om na zes maanden nog te begrijpen waarom een regel zo is geschreven.
En documenteer de bypass-rechten. Elke organisatie heeft een beheerder die alles kan zien. Dat is soms noodzakelijk. Maar als niemand weet wie dat zijn, is het een beveiligingsrisico dat geen enkele policy kan oplossen.
Aanbevolen bronnen voor verdere verdieping
Gebruik deze bronnen als startpunt voor implementatie en verificatie:
- Row-level security (RLS) met Power BI: de officiële Microsoft-documentatie met stappen voor rollen, DAX-voorbeelden en beheer in de service.
- PostgreSQL: Row Security Policies: de volledige referentie voor
CREATE POLICY,USING,WITH CHECKen het gedrag vanBYPASSRLS. - Row-Level Security in PostgreSQL: hoe dataisolatie werkt: praktische gids met voorbeelden voor tenant-isolatie en ontwerpkeuzes.
- Inleiding tot beveiliging op rijniveau in SQL Server: stappenplan voor inline filterfuncties en security policies in SQL Server.
- Database Security: Comprehensive Protection Frameworks: aanbevelingen voor het combineren van encryptie, toegangscontrole en monitoring voor gevoelige data.
Laat de implementatie en periodieke audits toetsen door een technisch contact of IT-partner, zeker wanneer gevoelige persoonsgegevens onder de AVG vallen. Dit artikel biedt algemene technische informatie en vervangt geen professioneel beveiligingsadvies.
Veelgestelde vragen
Wat is row level security precies?
Beveiliging op rijniveau is een techniek waarbij een database of rapportagetool automatisch filtert welke rijen een gebruiker ziet, op basis van zijn identiteit. Elke gebruiker ziet alleen de rijen waarvoor hij toestemming heeft, ook al gebruiken meerdere gebruikers hetzelfde rapport of dezelfde query.
Moet je beveiliging op rijniveau altijd inschakelen?
Niet altijd, maar wel wanneer meerdere gebruikers toegang hebben tot dezelfde dataset en niet alle rijen voor iedereen bestemd zijn. Voor gevoelige data zoals persoonsgegevens of financiële informatie is het sterk aan te raden, zeker in combinatie met kolommaskering en audit logging.
Wat is het verschil tussen rij- en kolomniveau beveiliging?
Beveiliging op rijniveau bepaalt welke rijen zichtbaar zijn; kolomniveau beveiliging (ook wel kolommaskering) bepaalt welke kolommen zichtbaar zijn. De twee technieken vullen elkaar aan en worden in een volledige beveiligingsstrategie gecombineerd.
Hoe goed werkt beveiliging op rijniveau in de praktijk?
Het werkt betrouwbaar wanneer policies eenvoudig zijn, sleutelkolommen geïndexeerd zijn en bypass-rechten beperkt zijn tot beheerders. Complexe predicaten met joins of niet-geïndexeerde kolommen leiden tot prestatieproblemen. Regelmatig testen na schema-wijzigingen is noodzakelijk om onbedoelde lekken te voorkomen.
Wat is het deny-all effect bij PostgreSQL?
Zodra je beveiliging op rijniveau inschakelt op een tabel in PostgreSQL zonder policies aan te maken, ziet niemand meer rijen. Dit is het standaardgedrag. Maak altijd eerst de benodigde policies aan in een testomgeving voordat je dit in productie activeert.
