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
| Rol | Stok | Satış | 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.