Supabase Row Level Security ile Yetkilendirme: Kim Neyi Görsün?

Çok şubeli bir uygulamada her kullanıcının sadece kendi verisini görmesini nasıl sağlarsınız? Supabase RLS ile veritabanı seviyesinde güvenlik rehberi.

Sorun: Frontend Kontrolü Yeterli Değil

Çok şubeli bir uygulama geliştirdiniz. Adana şubesi sadece Adana verilerini, Merkez sadece kendi verilerini görmeli. Frontend’te if kontrolü yaptınız:

// ❌ Güvenli DEĞİL
if (user.role === 'admin') {
  // tüm veriyi göster
} else {
  // sadece kendi şubesini göster
}

Sorun: Bir kullanıcı tarayıcının geliştirici konsolunu açar, API’yi doğrudan çağırır ve tüm şubelerin verisini çeker. Frontend kontrolü sadece UX içindir, güvenlik değildir.

Çözüm: Güvenliği veritabanı seviyesinde sağlamak. PostgreSQL’in Row Level Security (RLS) özelliği tam bunu yapar.

RLS Nedir?

RLS, veritabanının kendisinin “bu kullanıcı bu satırı görebilir mi?” diye kontrol etmesidir. Nasıl çağrılırsa çağrılsın — frontend, API, konsol — güvenlik çalışır.

Frontend kontrolü:  if (isAdmin) → göster
                    ↓ Bypass edilebilir ❌

RLS kontrolü:       PostgreSQL → "Bu kullanıcının şubesi bu satırla eşleşiyor mu?"
                    ↓ Bypass edilemez ✅

Temel Kullanım

1. RLS’i Etkinleştirme

-- Tabloda RLS'i aç
ALTER TABLE orders ENABLE ROW LEVEL SECURITY;

Bu komuttan sonra, tablo artık hiç kimseye veri göstermez (admin rolleri hariç). Erişim için policy (kural) yazmanız gerekir.

2. Okuma Kuralı

-- Tüm giriş yapmış kullanıcılar ürünleri görebilir
CREATE POLICY "urunleri_oku" ON products
FOR SELECT TO authenticated
USING (true);

3. Şube Bazlı Kısıtlama

-- Kullanıcı sadece yetkili olduğu şubelerin siparişlerini görsün
CREATE POLICY "siparis_sube_filtre" ON orders
FOR SELECT TO authenticated
USING (
  EXISTS (
    SELECT 1 FROM user_roles
    WHERE user_email = auth.jwt() ->> 'email'
    AND (
      role = 'admin'                    -- Admin her şeyi görür
      OR branch = orders.target_branch  -- Diğerleri kendi şubesini
    )
  )
);

Bu kural şunu der: “Eğer kullanıcı admin ise her şeyi görsün. Değilse sadece kendi şubesine ait siparişleri görsün.”

Rol Hiyerarşisi Örneği

RolStokSatışSiparişAdmin
Admin
Yönetici
Şube Müdürü✅ (kendi şubesi)
Temsilci🔒 (kendi)

Yönetici Kontrolü İçin Yardımcı Fonksiyon

CREATE FUNCTION is_manager_or_admin()
RETURNS BOOLEAN AS $$
BEGIN
  RETURN EXISTS (
    SELECT 1 FROM user_roles
    WHERE user_email = auth.jwt() ->> 'email'
    AND role IN ('admin', 'manager', 'branch_manager')
  );
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;

-- Sadece yöneticiler yaşlandırma verisi yükleyebilsin
CREATE POLICY "yaslandirma_yukle" ON customer_aging
FOR INSERT TO authenticated
USING (is_manager_or_admin());

Sık Yapılan Hatalar

1. Şube adlarında büyük/küçük harf uyuşmazlığı

// ❌ "MERKEZ" ≠ "merkez" — RLS eşleşmez
const hasAccess = allowedBranches.includes(order.target_branch);

// ✅ Her iki değeri de küçük harfe çevirin
const hasAccess = allowedBranches
  .map(b => b.toLowerCase())
  .includes(order.target_branch?.toLowerCase());

2. RLS’i açtığınız halde policy yazmayı unutmak

Sonuç: Tablo hiç veri döndürmez. Kullanıcı “veriler kayboldu” der.

3. RPC fonksiyonlarının RLS’i bypass etmesi

SECURITY DEFINER fonksiyonlar RLS’i atlar. Detaylı yazımıza bakın.

Debugging: “Neden Veri Gelmiyor?”

-- 1. Policy'leri listele
SELECT * FROM pg_policies WHERE tablename = 'orders';

-- 2. Admin olarak veriyi kontrol et
SET ROLE postgres;
SELECT COUNT(*) FROM orders WHERE branch = 'MERKEZ';

-- 3. Normal kullanıcı olarak dene
SET ROLE authenticated;
SELECT COUNT(*) FROM orders WHERE branch = 'MERKEZ';
-- Sonuç farklıysa → RLS engelliyor

Yeni Tablo Ekleme Checklist’i

Her yeni tablo oluşturduğunuzda:

-- 1. RLS aç
ALTER TABLE yeni_tablo ENABLE ROW LEVEL SECURITY;

-- 2. Okuma kuralı ekle
CREATE POLICY "yeni_tablo_oku" ON yeni_tablo
FOR SELECT TO authenticated USING (true);

-- 3. Gerekirse yazma kuralı ekle
CREATE POLICY "yeni_tablo_yaz" ON yeni_tablo
FOR INSERT TO authenticated USING (is_manager_or_admin());

Özet

  • Frontend güvenlik kontrolü kullanıcı deneyimi içindir
  • Gerçek güvenlik veritabanında (RLS) olmalıdır
  • RLS bir kez kurulunca, API nasıl çağrılırsa çağrılsın güvenlik sağlanır

Bu yazıdaki yapı çoğu proje için yeterlidir. 9 farklı rol, çoklu şube erişimleri ve modül bazlı yetkilendirme gerektiren daha karmaşık senaryolarda ise mimarinin derinleşmesi gerekir.


Bu yazı AstaFlow Case Study serisinin bir parçasıdır.