Row Level Security (RLS) ist eine PostgreSQL-Funktion, die einschränkt, auf welche Zeilen ein Datenbankbenutzer zugreifen kann. Supabase aktiviert RLS standardmäßig bei neuen Tabellen und empfiehlt, Richtlinien für jede Tabelle zu schreiben.
Kit verfolgt einen anderen Ansatz — es verwendet Sicherheit auf Anwendungsebene statt datenbankbasierter RLS. Diese Seite erklärt warum, wie es funktioniert und wann du möglicherweise zusätzliche RLS-Richtlinien hinzufügen möchtest.
Kits Sicherheitsmodell
Kit sichert den Datenbankzugriff auf der Anwendungsebene mit drei Mechanismen:
- Clerk-Authentifizierung überprüft die Identität des Benutzers bei jeder Anfrage über Middleware
- Explizite userId-Filterung stellt sicher, dass jede Abfrage nur Daten zurückgibt, die dem authentifizierten Benutzer gehören
- Das Repository Pattern erzwingt diese Zugriffsmuster konsistent im gesamten Codebase
Wie es in der Praxis funktioniert
Jede Datenbankabfrage in Kit enthält einen
userId-Filter. Es ist für den Anwendungscode unmöglich, versehentlich Daten eines anderen Benutzers zurückzugeben, weil die Abfragefunktionen eine Benutzer-ID als Parameter erfordern:typescript
// src/lib/db/queries/files.ts — Eigentümerprüfung ist in die Funktionssignatur eingebaut
export async function getUserFileById(
id: string,
userId: string // Aufrufer muss die ID des authentifizierten Benutzers angeben
): Promise<File | null> {
return prisma.file.findFirst({
where: {
id,
userId, // Gibt die Datei nur zurück, wenn sie diesem Benutzer gehört
},
})
}
Der aufrufende Code in API-Routen und Server Actions übergibt immer die authentifizierte Benutzer-ID von Clerk:
typescript
// In einer API-Route oder Server Action
const { userId } = await getServerAuth()
if (!userId) throw new Error('Unauthorized')
const file = await getUserFileById(fileId, userId)
if (!file) throw new Error('Not found') // Deckt auch unbefugten Zugriff ab
Dieses Muster wiederholt sich im gesamten Codebase — Subscription-Abfragen filtern nach
userId, Credit-Transaktionen filtern nach userId, KI-Konversationen filtern nach userId. Die Sicherheitsgrenze liegt im TypeScript-Code, nicht in SQL-Richtlinien.Warum Sicherheit auf Anwendungsebene?
Kit hat diesen Ansatz aus drei praktischen Gründen gewählt:
1. Prisma verbindet sich mit dem Service Role Key. Prisma verwendet den
DATABASE_URL-Verbindungsstring, der sich als Datenbankbesitzer (oder eine privilegierte Rolle bei Supabase) authentifiziert. Diese Rolle umgeht standardmäßig alle RLS-Richtlinien. Das Hinzufügen von RLS-Richtlinien zu Tabellen würde einen Fehler im Anwendungscode nicht schützen, da Prismas Verbindung unabhängig davon vollen Zugriff hat.2. Typsicherheit erkennt Fehler zur Kompilierzeit. Jede Abfragefunktion hat einen
userId: string-Parameter in ihrer TypeScript-Signatur. Wenn du ihn vergisst, erkennt der Compiler es, bevor der Code ausgeführt wird. Das ist eine stärkere Garantie als RLS, das erst zur Laufzeit fehlschlägt.3. Einfacher zu verstehen. Die Sicherheitslogik liegt neben der Geschäftslogik in TypeScript, nicht in einer separaten SQL-Schicht. Wenn du eine API-Route liest, siehst du genau, auf welche Daten sie zugreift, ohne Datenbankrichtlinien zu referenzieren.
Dies ist eine bewusste Architekturentscheidung, kein Versehen. Viele produktive SaaS-Anwendungen verwenden ausschließlich Sicherheit auf Anwendungsebene. Datenbankbasierte RLS bietet in bestimmten Szenarien einen Mehrwert — siehe unten.
Wann RLS hinzufügen
Es gibt Szenarien, in denen das Hinzufügen von Supabase RLS-Richtlinien eine sinnvolle zusätzliche Sicherheit bietet:
Direkter Supabase-Client-Zugriff
Wenn du den Supabase JavaScript-Client (nicht Prisma) für Datenabfragen verwendest — beispielsweise bei Supabase Realtime-Subscriptions oder clientseitigen Abfragen — authentifizieren sich diese Anfragen mit dem Anon-Key und unterliegen RLS-Richtlinien. Ohne Richtlinien würden diese Abfragen vollständig blockiert (Supabase aktiviert RLS standardmäßig).
typescript
// Diese Abfrage verwendet den Supabase JS-Client, NICHT Prisma
// Sie respektiert RLS-Richtlinien und verwendet den Anon-Key
import { supabase } from '@/lib/db/supabase'
const { data } = await supabase
.from('File')
.select('*')
.eq('userId', userId)
Multi-Tenant-Anwendungen
Wenn du Kit zu einer Multi-Tenant-Anwendung erweiterst, bei der verschiedene Organisationen dieselbe Datenbank teilen, bietet RLS ein Sicherheitsnetz. Selbst wenn der Anwendungscode einen Fehler hat, verhindert RLS, dass Daten über Mandantengrenzen hinweg lecken.
Defense in Depth
Für hochsensible Daten (Finanzdaten, Gesundheitsdaten, personenbezogene Daten) folgt das Hinzufügen von RLS als zweite Schutzschicht dem Defense-in-Depth-Sicherheitsprinzip. Wenn ein Code-Fehler die anwendungsseitige Prüfung umgeht, blockiert die Datenbank weiterhin unbefugten Zugriff.
Regulatorische Anforderungen
Manche Compliance-Frameworks (SOC 2, HIPAA) können datenbankbasierte Zugriffskontrollen zusätzlich zu anwendungsseitigen Prüfungen erfordern. RLS-Richtlinien liefern prüfbare Nachweise für Datenisolierung.
RLS-Richtlinien hinzufügen
Wenn du dich entscheidest, RLS hinzuzufügen, findest du hier die Einrichtung für Kits Tabellen.
Denk daran, dass Prisma sich mit einer privilegierten Rolle verbindet, die RLS umgeht. Diese Richtlinien betreffen nur Abfragen, die über den Supabase JavaScript-Client (mit dem
anon- oder authentifizierten Key), Supabase Realtime oder Supabase Edge Functions durchgeführt werden. Dein bestehender Prisma-basierter Anwendungscode ist davon nicht betroffen.Schritt-für-Schritt-Einrichtung
1
RLS auf der Tabelle aktivieren
RLS ist bei Supabase standardmäßig aktiviert, aber wenn du Tabellen via Prisma-Migrationen erstellt hast, musst du es möglicherweise explizit aktivieren:
sql
-- Im Supabase SQL-Editor ausführen
ALTER TABLE "File" ENABLE ROW LEVEL SECURITY;
2
Hilfsfunktion für Auth erstellen
Eine Funktion erstellen, die die Benutzer-ID aus dem Supabase-JWT-Token extrahiert. Diese wird in allen Richtlinien verwendet:
sql
-- Clerk-Benutzer-ID aus dem Supabase Auth-JWT extrahieren
-- Die Benutzer-ID ist im 'sub'-Claim gespeichert
CREATE OR REPLACE FUNCTION auth.clerk_user_id()
RETURNS TEXT AS $$
SELECT nullif(
current_setting('request.jwt.claims', true)::json->>'sub',
''
)
$$ LANGUAGE sql STABLE;
Kit verwendet Clerk für die Authentifizierung, nicht Supabase Auth. Wenn du möchtest, dass RLS-Richtlinien mit Clerk-Tokens funktionieren, musst du Supabase so konfigurieren, dass es Clerk-JWTs akzeptiert. Lies die Clerk + Supabase-Integrationsdokumentation für Einrichtungsanweisungen.
3
SELECT-Richtlinien schreiben
Benutzern erlauben, nur ihre eigenen Daten zu lesen:
sql
-- Benutzer können nur ihre eigenen Dateien lesen
CREATE POLICY "Users can view own files"
ON "File"
FOR SELECT
USING ("userId" = auth.clerk_user_id());
-- Benutzer können nur ihre eigene Subscription lesen
CREATE POLICY "Users can view own subscription"
ON "Subscription"
FOR SELECT
USING ("userId" = (
SELECT id FROM "User" WHERE "clerkId" = auth.clerk_user_id()
));
-- Benutzer können nur ihre eigenen Credit-Transaktionen lesen
CREATE POLICY "Users can view own transactions"
ON "CreditTransaction"
FOR SELECT
USING ("userId" = (
SELECT id FROM "User" WHERE "clerkId" = auth.clerk_user_id()
));
4
INSERT/UPDATE/DELETE-Richtlinien schreiben
Schreibzugriff steuern:
sql
-- Benutzer können nur Dateien für sich selbst erstellen
CREATE POLICY "Users can upload own files"
ON "File"
FOR INSERT
WITH CHECK ("userId" = (
SELECT id FROM "User" WHERE "clerkId" = auth.clerk_user_id()
));
-- Benutzer können nur ihre eigenen Dateien löschen
CREATE POLICY "Users can delete own files"
ON "File"
FOR DELETE
USING ("userId" = (
SELECT id FROM "User" WHERE "clerkId" = auth.clerk_user_id()
));
5
Richtlinien testen
Den Supabase SQL-Editor zur Überprüfung verwenden. Zur
anon-Rolle wechseln und testen:sql
-- JWT-Claims setzen, um einen authentifizierten Benutzer zu simulieren
SET request.jwt.claims = '{"sub": "user_test_clerk_id"}';
-- Dies sollte nur Dateien für den simulierten Benutzer zurückgeben
SET ROLE anon;
SELECT * FROM "File";
RESET ROLE;
Richtlinien-Beispiele für alle Tabellen
RLS mit Prisma kombinieren
Wenn du RLS-Richtlinien hinzufügst, beachte, wie verschiedene Verbindungsmethoden mit ihnen interagieren:
| Verbindung | Rolle | RLS angewendet? | Anwendungsfall |
|---|---|---|---|
Prisma (DATABASE_URL) | Besitzer / Service Role | Nein — umgeht RLS | Serverseitige Abfragen, API-Routen, Server Actions |
Supabase JS (anon-Key) | anon-Rolle | Ja | Clientseitige Abfragen, Realtime-Subscriptions |
Supabase JS (service_role-Key) | Service Role | Nein — umgeht RLS | Serverseitige Admin-Operationen |
| Supabase Edge Functions | Hängt vom verwendeten Key ab | Hängt ab | Serverlose Funktionen |
Den Anon-Key für RLS-erzwungene Abfragen verwenden
Wenn du Prisma-ähnliche Abfragen benötigst, die RLS respektieren, verwende den Supabase JavaScript-Client mit dem
anon-Key:typescript
import { createClient } from '@supabase/supabase-js'
// Client mit Anon-Key — RLS-Richtlinien werden erzwungen
const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
)
// Diese Abfrage wird durch RLS gefiltert — gibt nur die eigenen Dateien des Benutzers zurück
const { data: files } = await supabase
.from('File')
.select('*')
Wann welche Methode verwenden
| Szenario | Prisma verwenden | Supabase JS (anon) verwenden |
|---|---|---|
| Server Components | Ja | Nein |
| API-Routen | Ja | Nein |
| Server Actions | Ja | Nein |
| Clientseitige Abfragen | Nein | Ja |
| Realtime-Subscriptions | Nein | Ja |
| Admin-Operationen | Ja | Nein |
In der Praxis benötigen die meisten Kit-Anwendungen nur Prisma für den Datenbankzugriff. Der Supabase JS-Client wird hauptsächlich für Supabase Storage (Datei-Uploads) und Supabase Realtime (Live-Updates) verwendet. Wenn du diese Features nicht nutzt, benötigst du möglicherweise überhaupt keine RLS-Richtlinien.
Zusammenfassung
- Kit verwendet standardmäßig Sicherheit auf Anwendungsebene — Clerk-Authentifizierung und explizite userId-Filterung in jeder Abfragefunktion.
- Prisma umgeht RLS — Das Hinzufügen von Richtlinien ändert nichts am Verhalten deines bestehenden serverseitigen Codes.
- RLS hinzufügen, wenn du den Supabase JS-Client direkt verwendest, Multi-Tenant-Features baust oder Defense-in-Depth für Compliance benötigst.
- RLS ist optional — Der Ansatz auf Anwendungsebene mit TypeScript-Typsicherheit ist ein gültiges und weit verbreitetes Sicherheitsmodell.