Moderní tvorba webových aplikací
O webu

Row Level Security: Zabezpečení na úrovni řádků v databasi

Co je Row Level Security (RLS), jak funguje v PostgreSQL a dalších databasích, praktické příklady použití a výhody oproti aplikační logice.

33 minut

Row Level Security (RLS) je bezpečnostní funkce databasí, která umožňuje omezit přístup k jednotlivým řádkům v tabulce na základě definovaných pravidel. Místo aby aplikace kontrolovala, která data může uživatel vidět, tuto kontrolu provádí přímo database.

Row Level Security – schéma zabezpečení na úrovni řádků

Jak RLS funguje

Představte si tabulku s tisíci záznamy, kde každý uživatel má vidět jen své vlastní data. Bez RLS musí aplikace do každého SQL dotazu přidat podmínku:

SELECT * FROM documents WHERE user_id = current_user_id

S RLS tuto kontrolu dělá database automaticky. Stačí definovat politiku (policy) jednou a všechny dotazy se jí budou řídit:

SELECT * FROM documents  -- database automaticky vrátí jen data aktuálního uživatele

Kde se RLS vzalo

RLS není vynález PostgreSQL. Koncept vznikl v Oracle 8i v roce 1999 pod názvem Virtual Private Database (VPD). PostgreSQL přidal RLS až v roce 2016 – o 17 let později.

  • 1999 – Oracle 8i: Virtual Private Database (VPD)
  • 2015 – SQL Server CTP (preview): Row-Level Security
  • Leden 2016 – PostgreSQL 9.5: Row Level Security
  • Červen 2016 – SQL Server 2016: Row-Level Security

Původ názvu: Oracle používal název „Virtual Private Database”, který se neujal jako obecný termín. Termín „Row Level Security” se objevil nezávisle v PostgreSQL i SQL Serveru přibližně ve stejné době (2015–2016). Díky open-source povaze PostgreSQL a platformám jako Supabase se rozšířil nejvíc.

Proč používat RLS

  • Bezpečnost na úrovni database – nelze obejít chybou v aplikačním kódu

  • Jednodušší kód – nemusíte do každého dotazu přidávat WHERE podmínky

  • Centralisovaná pravidla – oprávnění jsou definovaná na jednom místě

  • Multi-tenant aplikace – snadné oddělení dat různých zákazníků

  • Omezení dopadu SQL injection – i při úspěšném útoku útočník neuvidí cizí data (ale RLS nenahrazuje ochranu proti injection!)

Přímý přístup z frontendu

Jednou z nejzajímavějších výhod RLS je možnost volat databasi přímo z JavaScriptu na frontendu, bez nutnosti psát backend API. Platformy jako Supabase jsou přímo navržené pro tento přístup – není to hack ani kompromis, ale doporučený způsob práce.

Tradiční přístup bez RLS

Frontend JavaScript Backend API kontrola oprávnění WHERE user_id = ? Database PostgreSQL request SQL ⚠️ Backend musí ručně přidávat WHERE podmínky do každého dotazu

Tento přístup vyžaduje psát a udržovat backend kód pro každou operaci.

Přístup s RLS

Frontend JavaScript Database + RLS PostgreSQL / Supabase 🛡️ RLS Policy user_id = auth.uid() přímý přístup ✅ Database automaticky filtruje data – není potřeba backend

Výhody tohoto přístupu:

  • Méně kódurychlejší vývoj – není potřeba psát REST/GraphQL API pro CRUD operace (Create, Read, Update, Delete)
  • Bezpečnost zaručená DB – nelze obejít, i když frontend kód je kompromitován
  • Real-time aktualisace – snadná integrace s WebSockets/subscriptions

Proč je to bezpečné (a kdy ne)

Frontend se nepřipojuje k PostgreSQL přímo! Mezi frontendem a databasí je API vrstva (PostgREST u Supabase), která zajišťuje bezpečnost:

Frontend → Supabase API (PostgREST) → PostgreSQL + RLS
               ↑
          rate limiting
          validace požadavků
          connection pooling
          timeouty
          žádné surové SQL

Přímé připojení k PostgreSQL z frontendu je bezpečnostní katastrofa:

  • Connection string (včetně hesla) je viditelný v DevTools
  • Útočník může spouštět libovolné SQL (DROP TABLE, DELETE FROM)
  • RLS nepomůže – útočník má plné credentials vlastníka
  • Žádný rate limiting ani ochrana proti DoS

Pravidlo: PostgreSQL connection string nikdy nepatří do frontend kódu. Supabase používá veřejný anon key + JWT tokeny – to je zásadní rozdíl.

Historie přímého přístupu z frontendu

Přímý přístup z frontendu není vynález Supabase. Vývoj tohoto přístupu:

  • 2012 – Firebase – první masově populární řešení pro přímý přístup z frontendu. NoSQL database se Security Rules. Ukázal, že tento přístup funguje ve velkém měřítku.
  • 2014 – PostgREST – open-source projekt, který automaticky vytváří REST API z PostgreSQL schématu. Původně využíval PostgreSQL role a GRANT/REVOKE, po vydání PostgreSQL 9.5 přidal podporu RLS.
  • 2016 – PostgreSQL 9.5 – přidává nativní Row Level Security, což je základ pro bezpečný přímý přístup.
  • 2020 – Supabase – vzal PostgREST a udělal z něj managed službu s auth, storage a hezkým SDK. Zpopularizoval přímý přístup k PostgreSQL pro široké publikum.
  • 2025 – Neon Data API – druhá managed služba s vestavěným PostgREST.

Supabase interně používá PostgREST – jeho SDK je jen hezčí wrapper:

// PostgREST API (přímé volání)
fetch('https://api.example.com/posts?user_id=eq.123')

// Supabase SDK (wrapper kolem PostgREST)
supabase.from('posts').select('*').eq('user_id', 123)

Supabase nevynalezl přímý přístup – udělal ho snadným a přístupným pro PostgreSQL.

Alternativy k Supabase

Supabase není jediná platforma umožňující bezpečný přístup z frontendu. Neon je jediná další služba s vestavěným PostgREST, ostatní platformy jdou cestou GraphQL nebo vlastního API:

  • Neon – serverless PostgreSQL s vestavěným PostgREST (Data API od 2025). Nabízí unikátní funkce jako database branching. Jediná přímá alternativa k Supabase s PostgREST.
  • Hasura – GraphQL engine pro PostgreSQL s propracovaným systémem permissions. Lze nasadit self-hosted nebo jako cloud službu.
  • Nhost – open-source alternativa k Supabase, postavená na PostgreSQL + Hasura GraphQL. Nabízí auth, storage i serverless functions.
  • Firebase – Google platforma s NoSQL databasí (Firestore) a Security Rules. Jiný přístup než RLS, ale stejný princip – bezpečnost na úrovni database.
  • PocketBase – jednoduchý self-hosted backend v jednom Go binárce. SQLite database s pravidly přístupu definovanými v administraci.
  • Appwrite – open-source BaaS s vlastní databasí, auth a permissions systémem. Self-hosted nebo cloud.

Všechny tyto platformy sdílejí klíčový princip: frontend komunikuje přes bezpečné API, ne přímo s databasí, a oprávnění jsou vynucována na serverové straně.

Proč AI nástroje používají Supabase

Všimli jste si, že AI nástroje pro generování aplikací (Bolt, Lovable, v0) často používají právě Supabase? Není to náhoda.

AI generuje primárně frontend kód (React, Svelte, Vue). Díky přímému přístupu k databasi nepotřebuje generovat backend:

  • Bez Supabase – AI musí generovat frontend + backend, řešit hosting, psát API endpoints, implementovat autentizaci
  • Se Supabase – AI generuje jen frontend, vše ostatní je hotové

Supabase funguje jako „backend v jednom řádku”:

const supabase = createClient(url, anonKey)

A máte auth, databasi, storage i realtime – vše volatelné přímo z frontendu. AI nástroj vygeneruje React komponentu, připojí Supabase klienta a má fungující aplikaci bez jediného řádku backendového kódu.

Praktický příklad (Supabase)

// Nastavení RLS v databasi (jednou)
CREATE POLICY "Users can read own posts" ON posts
  FOR SELECT USING (auth.uid() = user_id);

// Frontend kód - přímý přístup k DB
import { createClient } from '@supabase/supabase-js'

const supabase = createClient(url, anonKey)

// Přihlášení uživatele
await supabase.auth.signInWithPassword({ email, password })

// Čtení dat - RLS automaticky vrátí jen data aktuálního uživatele
const { data } = await supabase
  .from('posts')
  .select('*')  // Žádné WHERE user_id! RLS to dělá automaticky

// Vkládání dat
const { data } = await supabase
  .from('posts')
  .insert({ title: 'Nový příspěvek', content: '...' })

Díky RLS je zaručeno, že uživatel vidí a mění jen svá data, i když volá databasi přímo z prohlížeče. Toto je standardní a doporučený způsob práce se Supabase – tisíce produkčních aplikací takto fungují.

Kdy přidat backend (Edge Functions)

Většina aplikací může fungovat pouze s přímým přístupem z frontendu. Supabase má vestavěné ochrany (rate limiting, query timeout, connection pooling), takže pro běžné CRUD operace (čtení, zápis, úprava, mazání) nepotřebujete nic dalšího.

Backend (nebo Supabase Edge Functions – serverless funkce běžící na edge serverech blízko uživatelům) přidejte pouze pro:

  • Platby – komunikace s platební bránou (Stripe, PayPal)
  • Integrace s 3rd party API – kde potřebujete skrýt API klíče
  • Složitou business logiku – validace napříč více tabulkami, výpočty
  • Odesílání emailů – triggery po akcích uživatele

Pro většinu aplikací platí: začněte s přímým přístupem a backend přidávejte jen když narazíte na konkrétní potřebu.

Bezpečnostní aspekty a úskalí

RLS + aplikační validace (Defense in Depth)

Ano, kombinace RLS s ověřováním v aplikaci je doporučená praxe! Jde o princip „obrany do hloubky”:

  • Frontend validace – kontrola formátu, UX feedback, rychlá odezva
  • Backend validace (pokud existuje) – business pravidla, složitější kontroly
  • RLS v databasi – poslední a nejdůležitější obrana, kterou nelze obejít
// Frontend validace - rychlá odezva pro uživatele
if (!title || title.length < 3) {
  return { error: 'Název musí mít alespoň 3 znaky' }
}

// Volání DB s RLS - i kdyby frontend validace selhala,
// RLS zajistí, že uživatel může upravit jen své záznamy
await supabase
  .from('posts')
  .update({ title })
  .eq('id', postId)  // RLS automaticky ověří vlastnictví

Nikdy nespoléhejte jen na frontend validaci – ta může být obejita otevřením DevTools. RLS je vaše poslední pojistka.

Úskalí přímého přístupu z frontendu

1. Bezpečnost credentials

  • Frontend používá anonymní klíč (anon key), který je veřejný a všichni ho vidí
  • Databasové heslo NIKDY nesmí být ve frontend kódu
  • Supabase používá JWT tokeny – database rozlišuje uživatele podle auth.uid() z tokenu
  • Service role klíč (s admin právy) patří jen na backend

2. Validace dat

-- Špatně: Frontend může poslat cokoliv
CREATE TABLE posts (
  title TEXT,
  content TEXT
);

-- Lépe: DB constraints jako další vrstva ochrany
CREATE TABLE posts (
  title TEXT NOT NULL CHECK (length(title) >= 3 AND length(title) <= 200),
  content TEXT NOT NULL CHECK (length(content) <= 50000),
  created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

3. Rate limiting a DoS útoky

  • Frontend může posílat neomezené množství dotazů
  • Řešení: Supabase má vestavěný rate limiting, nebo použít Edge Functions
  • Pro kritické operace použít backend API s vlastním rate limitingem

4. Náročné dotazy a DoS útoky

Ano, útočník může záměrně posílat výkonnostně náročné dotazy! To je jeden z hlavních bezpečnostních problémů přímého přístupu.

// Útočník může poslat náročný dotaz z DevTools:
await supabase
  .from('posts')
  .select('*, comments(*, author(*)), likes(*, user(*))')
  .limit(10000)  // Načte tisíce záznamů s vnořenými JOINy

Jak to řeší Supabase:

  • Query timeout – dotazy delší než X sekund (typicky 8–30s) jsou automaticky zabity
  • Max rows limit – omezení maximálního počtu vrácených řádků (default 1000)
  • Connection pooling – omezený počet souběžných spojení na projekt
  • Rate limiting na API – limit požadavků za minutu podle tier (60/min na free, 500+/min na Pro)
  • Statement timeout – PostgreSQL konfigurace statement_timeout
  • Resource limits – paměť a CPU jsou omezené podle tarifu

Dodatečná ochrana, kterou můžete implementovat:

-- Vytvořit VIEW s předem optimalisovaným dotazem
CREATE VIEW posts_with_stats AS
SELECT
  p.*,
  COUNT(DISTINCT c.id) as comment_count,
  COUNT(DISTINCT l.id) as like_count
FROM posts p
LEFT JOIN comments c ON c.post_id = p.id
LEFT JOIN likes l ON l.post_id = p.id
GROUP BY p.id;

-- RLS platí i na VIEW
ALTER VIEW posts_with_stats SET (security_barrier = true);

-- Frontend pak volá VIEW místo složitého dotazu
const { data } = await supabase
  .from('posts_with_stats')
  .select('*')
  .limit(20)  // Přiměřený limit

Alternativně použít PostgreSQL funkci s limity:

-- Funkce s vestavěným limitem
CREATE FUNCTION get_user_posts(user_id UUID, max_limit INT DEFAULT 100)
RETURNS SETOF posts AS $
BEGIN
  IF max_limit > 100 THEN
    RAISE EXCEPTION 'Limit cannot exceed 100';
  END IF;

  RETURN QUERY
  SELECT * FROM posts
  WHERE author_id = user_id
  LIMIT max_limit;
END;
$ LANGUAGE plpgsql SECURITY DEFINER;
-- Pozor: SECURITY DEFINER obchází RLS! Funkce běží s právy vlastníka.
-- Bezpečnost zajišťuje WHERE podmínka uvnitř funkce.

Best practices pro ochranu:

  • Vždy používejte LIMIT – nikdy nenačítejte neomezené množství dat
  • Views pro složité dotazy – kontrolujete, co lze dělat
  • Index na sloupce v RLS – jinak každý dotaz dělá full table scan
  • Monitoring – sledujte pomalé dotazy v Supabase dashboardu
  • Expensive operations přes backend – agregace, reporty, statistiky
  • Edge Functions pro business logiku – middleware mezi frontendem a DB

5. N+1 problém

// ❌ Špatně: N+1 dotazů z frontendu
const posts = await supabase.from('posts').select('*')
for (const post of posts.data) {
  const author = await supabase.from('users').select('*').eq('id', post.user_id)
  // N dotazů!
}

// ✅ Lépe: JOIN v jednom dotazu
const posts = await supabase
  .from('posts')
  .select('*, author:users(*)')  // Supabase automaticky udělá JOIN

6. Citlivá data v odpovědích

  • I s RLS může database vrátit více dat, než byste chtěli zobrazit
  • Používejte .select() k výběru jen potřebných sloupců
  • Citlivá pole (hesla, tokeny) nastavte jako SECURITY DEFINER funkce nebo views
-- Vždy vybírejte jen potřebné sloupce
await supabase
  .from('users')
  .select('id, name, avatar_url')  // NE select('*')

7. Error messages a info leaks

  • Chybové hlášky z DB můžou prozradit strukturu tabulek
  • V produkci logujte detailní chyby, ale uživateli ukažte obecnou hlášku

8. Zapomenuté RLS nastavení – kritické bezpečnostní risiko!

Toto je jeden z nejnebezpečnějších problémů RLS! Pokud vytvoříte tabulku během vývoje a zapomenete nastavit RLS, aplikace funguje normálně – a právě to je problém.

-- ❌ NEBEZPEČNÉ: Tabulka bez RLS
CREATE TABLE private_documents (
  id SERIAL PRIMARY KEY,
  user_id UUID,
  secret_data TEXT
);

-- Aplikace funguje! Frontend může číst všechno.
-- Během vývoje to nikoho nebolí.
-- V produkci je to OBROVSKÁ bezpečnostní díra!

Výchozí chování PostgreSQL:

  • Bez RLS – tabulka je OTEVŘENÁ, všichni vidí všechna data
  • S RLS ale bez politik – tabulka je UZAMČENÁ, nikdo nic nevidí (kromě superusers)
  • S RLS a s politikami – funguje podle pravidel
-- Tabulka bez RLS
CREATE TABLE posts (...);
-- ✗ Všichni uživatelé vidí všechna data!

-- Tabulka s RLS ale bez politik
CREATE TABLE posts (...);
ALTER TABLE posts ENABLE ROW LEVEL SECURITY;
-- ✓ Nikdo nevidí nic (bezpečné, ale nefunkční)
-- ✗ V dev módu se zdá, že "nefunguje", tak se RLS vypne

-- Správně: RLS s politikami
CREATE TABLE posts (...);
ALTER TABLE posts ENABLE ROW LEVEL SECURITY;
CREATE POLICY user_posts ON posts USING (user_id = auth.uid());
-- ✓ Funguje a je bezpečné

Risikový scénář:

  1. Vývojář vytvoří tabulku během vývoje bez RLS
  2. Aplikace funguje (všichni vidí všechno, ale v dev to nevadí)
  3. Vývojář si řekne „RLS dodělám později”
  4. Funkce se nasadí do produkce
  5. BEZPEČNOSTNÍ DÍRA – všichni uživatelé vidí data všech ostatních!

Jak se bránit:

-- 1. RLS VŽDY jako první, ještě před vložením dat
CREATE TABLE sensitive_data (...);
ALTER TABLE sensitive_data ENABLE ROW LEVEL SECURITY;
-- Tabulka je teď uzamčená - bezpečné

-- 2. Pak vytvořit politiky
CREATE POLICY ... ON sensitive_data ...;

-- 3. Nebo použít FORCE ROW LEVEL SECURITY pro extra ochranu
ALTER TABLE sensitive_data FORCE ROW LEVEL SECURITY;
-- Platí i pro vlastníka tabulky a adminy!

Automatická kontrola v migraci:

-- Přidat do každé migrace kontrolu, že RLS je zapnuté
DO $
DECLARE
  tbl record;
BEGIN
  FOR tbl IN
    SELECT schemaname, tablename
    FROM pg_tables
    WHERE schemaname = 'public'
    AND tablename NOT IN ('migrations', 'schema_migrations')
  LOOP
    IF NOT EXISTS (
      SELECT 1 FROM pg_class c
      JOIN pg_namespace n ON n.oid = c.relnamespace
      WHERE n.nspname = tbl.schemaname
      AND c.relname = tbl.tablename
      AND c.relrowsecurity = true
    ) THEN
      RAISE EXCEPTION 'Tabulka %.% nemá zapnuté RLS!',
        tbl.schemaname, tbl.tablename;
    END IF;
  END LOOP;
END $;

CI/CD kontroly:

-- SQL skript pro CI/CD pipeline
-- Selže, pokud nějaká tabulka nemá RLS
SELECT
  schemaname,
  tablename,
  'CHYBÍ RLS!' as problem
FROM pg_tables
WHERE schemaname = 'public'
AND tablename NOT IN ('migrations')
AND NOT EXISTS (
  SELECT 1 FROM pg_class c
  JOIN pg_namespace n ON n.oid = c.relnamespace
  WHERE n.nspname = schemaname
  AND c.relname = tablename
  AND c.relrowsecurity = true
);

Supabase strategie:

  • Supabase Dashboard zobrazuje WARNING pro tabulky bez RLS
  • Lze nastavit výchozí politiku „deny all” pro nové tabulky
  • Policy editor v dashboardu znemožní nasazení bez politik

Best practice: „Secure by default”

-- Šablona pro KAŽDOU novou tabulku:

-- 1. Vytvořit tabulku
CREATE TABLE new_table (...);

-- 2. OKAMŽITĚ zapnout RLS
ALTER TABLE new_table ENABLE ROW LEVEL SECURITY;

-- 3. OKAMŽITĚ vytvořit základní politiky
CREATE POLICY select_own ON new_table
  FOR SELECT USING (user_id = auth.uid());

CREATE POLICY insert_own ON new_table
  FOR INSERT WITH CHECK (user_id = auth.uid());

-- 4. Teprve pak testovat a vyvíjet

Best practices pro přímý přístup

  • Vždy používejte RLS – nikdy nepovolte přístup k tabulce bez RLS politik
  • Kombinujte s DB constraints – NOT NULL, CHECK, UNIQUE jako další vrstva validace
  • Používejte Views pro složité dotazy – místo složitých JOINů z frontendu
  • Auditujte přístupy – logujte všechny operace pro analysu bezpečnosti
  • Testujte RLS politiky důkladně – zkuste obejít vlastní zabezpečení
  • Citlivé operace přes backend – platby, změna emailu, admin operace

RLS na backendu vs. WHERE podmínky

Pokud máte klasický backend (Node.js, PHP, Python), většina aplikací RLS nepoužívá. Místo toho přidávají WHERE podmínky v aplikačním kódu:

// Laravel (PHP)
$posts = Post::where('user_id', auth()->id())->get();

// Django (Python)
posts = Post.objects.filter(user_id=request.user.id)

// Prisma (Node.js)
const posts = await prisma.post.findMany({
  where: { userId: user.id }
})

Proč většina backendů RLS nepoužívá

  • ORM to nepodporují – Laravel Eloquent, Django ORM, Rails ActiveRecord, Prisma – všechny používají WHERE podmínky
  • Session proměnné – RLS vyžaduje nastavit SET app.user_id = X pro každý request
  • Přenositelnost – WHERE funguje na MySQL, PostgreSQL, SQLite… RLS je PostgreSQL-only
  • Kontrola v kódu – vývojáři chtějí vidět logiku v aplikaci, ne skrytou v databasi
  • Testovatelnost – WHERE podmínky jsou snazší testovat

Kdy použít RLS i na backendu

  • Přímý přístup z frontendu (Supabase, Neon) – RLS je nutnost
  • Multi-tenant jako extra vrstva – defense in depth, pojistka proti chybám v kódu
  • Compliance požadavky (GDPR, HIPAA) – vyžadují bezpečnost na více vrstvách
  • Citlivá data – zdravotnictví, finance, kde je potřeba maximální ochrana

Shrnutí: Na backendu je WHERE v ORM standardní praxe. RLS používejte pro přímý přístup z frontendu nebo jako extra vrstvu ochrany u citlivých dat.

RLS v PostgreSQL

PostgreSQL podporuje RLS od verse 9.5 a je to nejpoužívanější implementace.

Základní použití

Vytvoříme tabulku s dokumenty, kde každý uživatel vidí jen své záznamy:

-- Vytvoření tabulky
CREATE TABLE documents (
  id SERIAL PRIMARY KEY,
  title TEXT,
  content TEXT,
  user_id TEXT NOT NULL
);

-- Povolení RLS pro tabulku
ALTER TABLE documents ENABLE ROW LEVEL SECURITY;

-- Vytvoření politiky - uživatel vidí jen své řádky
CREATE POLICY user_documents ON documents
  FOR SELECT
  USING (user_id = current_user);

-- Politika pro vkládání - může vložit pouze se svým user_id
CREATE POLICY user_insert_documents ON documents
  FOR INSERT
  WITH CHECK (user_id = current_user);

Od teď každý uživatel automaticky vidí jen své dokumenty, bez nutnosti měnit aplikační dotazy.

Typy politik

RLS politiky lze definovat pro různé operace:

  • FOR SELECT – omezuje čtení dat
  • FOR INSERT – kontroluje vkládání nových řádků
  • FOR UPDATE – omezuje úpravu existujících řádků
  • FOR DELETE – kontroluje mazání řádků
  • FOR ALL – platí pro všechny operace

USING vs. WITH CHECK

  • USING – definuje, které existující řádky jsou viditelné
  • WITH CHECK – kontroluje, zda nové/upravené řádky splňují podmínku
-- Pro SELECT stačí USING
CREATE POLICY view_own_posts ON posts
  FOR SELECT
  USING (author_id = current_user);

-- Pro INSERT je důležité WITH CHECK
CREATE POLICY insert_own_posts ON posts
  FOR INSERT
  WITH CHECK (author_id = current_user);

-- Pro UPDATE často potřebujeme obojí
CREATE POLICY update_own_posts ON posts
  FOR UPDATE
  USING (author_id = current_user)      -- můžu upravit jen vlastní
  WITH CHECK (author_id = current_user); -- a nemůžu změnit autora

Praktické příklady

Multi-tenant aplikace

Aplikace, kde má každá firma své oddělené data:

-- Tabulka s ID organisace
CREATE TABLE tasks (
  id SERIAL PRIMARY KEY,
  title TEXT,
  organisation_id UUID NOT NULL
);

ALTER TABLE tasks ENABLE ROW LEVEL SECURITY;

-- Použití session proměnné pro aktuální organisaci
CREATE POLICY org_isolation ON tasks
  USING (organisation_id::text = current_setting('app.current_org_id'));

-- V aplikaci nastavíte před dotazy:
SET app.current_org_id = '123e4567-e89b-12d3-a456-426614174000';
SELECT * FROM tasks; -- vrátí jen úkoly této organisace

Administrátorský přístup

Admini vidí všechno, běžní uživatelé jen své data:

CREATE POLICY user_or_admin_access ON documents
  FOR SELECT
  USING (
    user_id = current_user
    OR current_user IN (SELECT username FROM admin_users)
  );

Veřejné vs. soukromé záznamy

CREATE TABLE posts (
  id SERIAL PRIMARY KEY,
  title TEXT,
  content TEXT,
  author_id TEXT,
  is_public BOOLEAN DEFAULT false
);

ALTER TABLE posts ENABLE ROW LEVEL SECURITY;

-- Každý vidí veřejné příspěvky, vlastní vidí všechny
CREATE POLICY view_posts ON posts
  FOR SELECT
  USING (is_public = true OR author_id = current_user);

Časové omezení

CREATE TABLE events (
  id SERIAL PRIMARY KEY,
  title TEXT,
  start_date TIMESTAMPTZ,
  end_date TIMESTAMPTZ
);

ALTER TABLE events ENABLE ROW LEVEL SECURITY;

-- Uživatel vidí jen aktuální a budoucí události
CREATE POLICY current_events ON events
  FOR SELECT
  USING (end_date > NOW());

RLS v Supabase

Supabase staví na PostgreSQL a RLS je jeho základní bezpečnostní mechanismus. Každá tabulka by měla mít definované RLS politiky.

Integrace s JWT

Supabase automaticky nastavuje PostgreSQL proměnné z JWT tokenu:

-- Přístup k user ID z JWT
CREATE POLICY user_data ON profiles
  FOR SELECT
  USING (auth.uid() = user_id);

-- Přístup k dalším JWT claims
CREATE POLICY premium_content ON articles
  FOR SELECT
  USING (
    is_public = true
    OR (auth.jwt() ->> 'subscription')::text = 'premium'
  );

Supabase Dashboard

Supabase má GUI pro správu RLS politik přímo v dashboardu, což zjednodušuje jejich vytváření a testování.

RLS v dalších databasích

Oracle Database

Oracle nazývá RLS jako Virtual Private Database (VPD) a podporuje ho již od verse 8i:

BEGIN
  DBMS_RLS.ADD_POLICY(
    object_schema   => 'hr',
    object_name     => 'employees',
    policy_name     => 'emp_policy',
    function_schema => 'hr',
    policy_function => 'employee_security',
    statement_types => 'SELECT, UPDATE, DELETE'
  );
END;

Microsoft SQL Server

SQL Server 2016+ podporuje RLS pomocí inline table-valued funkcí:

-- Bezpečnostní funkce
CREATE FUNCTION dbo.fn_securitypredicate(@UserId int)
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN SELECT 1 AS fn_securitypredicate_result
WHERE @UserId = CAST(SESSION_CONTEXT(N'UserId') AS int);

-- Aplikace politiky
CREATE SECURITY POLICY dbo.UserPolicy
ADD FILTER PREDICATE dbo.fn_securitypredicate(UserId) ON dbo.Documents
WITH (STATE = ON);

MySQL

MySQL nepodporuje nativní RLS. Alternativy:

  • Použití VIEW s WHERE podmínkami pro jednotlivé role
  • Aplikační logika v kódu
  • Migrace na PostgreSQL nebo jinou databasi s nativní podporou RLS

Výkonnostní aspekty

Indexy jsou klíčové

RLS přidává WHERE podmínky do každého dotazu. Bez správných indexů může být RLS pomalé:

-- Pokud máte politiku na user_id, vytvořte index
CREATE INDEX idx_documents_user_id ON documents(user_id);

-- Pro složitější politiky může být potřeba composite index
CREATE INDEX idx_posts_author_public ON posts(author_id, is_public);

Bypass RLS pro systémové účty

Některé procesy (migrace, admin skripty) potřebují vidět všechna data:

-- PostgreSQL: SUPERUSER nebo vlastník tabulky RLS obchází
-- Pro aplikační účty můžete použít:
ALTER TABLE documents FORCE ROW LEVEL SECURITY; -- platí i pro vlastníka

-- Nebo explicitly povolit bypass pro specifickou roli
ALTER ROLE admin_role BYPASSRLS;

Časté chyby a problémy

Zapomenuté povolení RLS

-- ❌ Zapomněli jste ENABLE ROW LEVEL SECURITY
CREATE POLICY user_policy ON users USING (id = current_user);
-- Politika existuje, ale nefunguje!

-- ✅ Správně:
ALTER TABLE users ENABLE ROW LEVEL SECURITY;
CREATE POLICY user_policy ON users USING (id = current_user);

Chybějící politiky pro operace

-- Máte politiku jen pro SELECT, ale ne INSERT
-- Uživatel nemůže vložit žádná data!

-- Řešení: přidat politiky pro všechny potřebné operace
CREATE POLICY user_select ON docs FOR SELECT USING (...);
CREATE POLICY user_insert ON docs FOR INSERT WITH CHECK (...);
CREATE POLICY user_update ON docs FOR UPDATE USING (...) WITH CHECK (...);

Nenastavené session proměnné

-- Politika používá current_setting()
CREATE POLICY org_policy ON data
  USING (org_id = current_setting('app.org_id'));

-- ❌ Pokud není proměnná nastavená, dotaz selže
-- ✅ Použijte default hodnotu:
USING (org_id = current_setting('app.org_id', true)::uuid)

Osvědčené postupy

  • Vždy používejte RLS pro multi-tenant data – je to nejspolehlivější ochrana proti data leakům

  • Testujte politiky důkladně – zkuste se přihlásit jako různí uživatelé a ověřte, co vidí

  • Kombinujte s application-level kontrolami – RLS je poslední obrana, ne jediná

  • Dokumentujte politiky – používejte komentáře k vysvětlení složitých pravidel

  • Monitorujte výkon – sledujte pomalé dotazy a přidávejte indexy podle potřeby

  • Používejte FORCE ROW LEVEL SECURITY pro citlivá data – aby RLS platilo i pro admin účty

Závěr

  • Row Level Security (RLS) omezuje přístup k jednotlivým řádkům tabulky přímo na úrovni database, místo aby to řešila aplikace

  • Hlavní výhody jsou vyšší bezpečnost (nelze obejít chybou v kódu), jednodušší aplikační logikacentralisovaná správa oprávnění

  • PostgreSQL má nejlepší podporu RLS a je základ pro platformy jako Supabase, které dělají RLS ještě dostupnější

  • RLS je ideální pro multi-tenant aplikace, kde každý zákazník má svá oddělená data a nesmí vidět data ostatních

  • Pro dobrý výkon je nutné mít správné indexy na sloupce použité v RLS politikách

  • Časté chyby zahrnují zapomenutí povolit RLS (ENABLE ROW LEVEL SECURITY), chybějící politiky pro INSERT/UPDATE/DELETE nebo nenastavené session proměnné

Odkazy jinam

Související články

Egress: Odchozí datový provoz a jeho náklady

Co je to egress traffic, jak funguje v cloudu, proč za něj (ne)platíte a jak optimalisovat náklady.

8 minut

Co jsou materializované pohledy a jak zvýší výkon database

Materializované pohledy jsou mocný nástroj pro optimalisaci databasových dotazů.

20 minut

Výpadek Cloudflare: Databasová chyba vyřadila tisíce webů

18. listopadu 2025 postihla Cloudflare čtyřhodinová porucha způsobená změnou oprávnění v databasi. Jednoduchý přehled, co se stalo a proč.

7 minut

Instalace Apache, PHP a MySQL za 30 vteřin

Jak si ve Windows spustit vlastní Apache, PHP a MySQL na svém PC za půl minuty.

7 minut

Novinky e-mailem

Když budu mít něco opravdu zajímavého, můžu vám to poslat e-mailem

Přidej se k 500+ čtenářům
Jen kvalitní obsah
Žádný spam

Web jecas.cz píše Bohumil Jahoda, kontakt
Seznam všech článků · Témata
2013–2026