Zum Inhalt springen
Alle Artikel

Mandantentrennung im SaaS mit Postgres RLS und Workspace-Auth

Kombiniere serverseitige Live-Mitgliedschaftsprüfungen mit indizierten Postgres-Row-Level-Security-Policies, damit Mandantendaten über Web, Mobile und Hintergrund-Jobs hinweg isoliert bleiben.

das supastack-Team2 Min. Lesezeit

Should I use Postgres Row-Level Security or application-level workspace checks?

It depends on how your application connects to Postgres. If browser or mobile clients query the database directly through PostgREST, Row-Level Security (RLS) is mandatory. If all traffic flows through a server-side TypeScript boundary, explicit membership checks on every query are your primary defense, and RLS serves as defense-in-depth against forgotten WHERE clauses. Combine live session-to-membership resolution with indexed workspace_id policies. supastack enforces server-side workspace guards across web, mobile, and desktop, and provides a free RLS policy generator for Supabase tables.

Anwendungs-Guards und Datenbank-Policies lösen unterschiedliche Fehler

Entwickler betrachten Autorisierung auf Anwendungsebene und Postgres Row-Level Security oft als Gegensätze. In der Praxis fangen sie unterschiedliche Fehler ab: Serverseitige Guards setzen Rollenhierarchien, Entitlements und Audit-Logs durch, bevor Geschäftslogik läuft, während RLS garantiert, dass eine vergessene WHERE workspace_id = ...-Bedingung niemals die Zeilen aller Mandanten ausgibt.

Egal ob du Supabase oder selbst gehostetes Postgres mit Drizzle nutzt: Jede mandanteneigene Tabelle sollte einen NOT NULL-Fremdschlüssel workspace_id tragen. Der Artikel zu Workspace-Grenzen erklärt, warum dieser Scope immer auf dem Server abgeleitet werden muss.

  • Anwendungs-Guards prüfen Sitzungsaktualität, Mitgliederrollen (owner, admin, member) und Feature-Berechtigungen.
  • Datenbank-RLS-Policies bilden ein hartes Sicherheitsnetz bei direkten Client-Abfragen oder rohen SQL-Helfern.
  • Beide Schichten stützen sich auf eine einzige Wahrheitsquelle: die Join-Tabelle workspace_members.

Prüfe die Live-Mitgliedschaft statt veralteten Cookies zu vertrauen

Eine currentWorkspaceId in einem Browser-Cookie oder Header zu speichern ist praktisch für die Navigation, aber ein Cookie ist eine Präferenz und keine Berechtigung. Wenn ein Admin ein Teammitglied aus einem Workspace entfernt, behält dessen Browser weiterhin das alte Cookie.

Verknüpfe die aktive Sitzung bei jeder Anfrage mit workspace_members. Fehlt die Mitgliedschaftszeile, lehne die Anfrage sofort ab, statt auf das Ablaufen eines JWT oder Cookies zu warten. Der Beitrag über Authentifizierung jenseits des Login-Formulars zeigt, wie Identität in diese Prüfung fließt.

export async function requireWorkspaceViewer(requestedWorkspaceId?: string) {
  const session = await requireAuthenticatedSession();
  const membership = await db.query.workspaceMembers.findFirst({
    where: and(
      eq(workspaceMembers.userId, session.user.id),
      requestedWorkspaceId
        ? eq(workspaceMembers.workspaceId, requestedWorkspaceId)
        : undefined,
    ),
    with: { workspace: true },
  });

  if (!membership) throw new ForbiddenError("Not a member of this workspace");
  return { user: session.user, workspace: membership.workspace, role: membership.role };
}

Schreibe schnelle, indizierte Row-Level-Security-Policies

Naive RLS-Policies, die auth.uid() direkt für jede Zeile aufrufen, zwingen Postgres dazu, die Funktion pro Tupel neu auszuwerten. Das Kapseln von (select auth.uid()) in einer skalaren Subquery erlaubt dem Query-Planner das Caching als initPlan, und ein zusammengesetzter Index auf (workspace_id, user_id) hält Mitgliedschafts-Lookups im Sub-Millisekunden-Bereich.

Mit dem kostenlosen Supabase-RLS-Policy-Generator kannst du sofort ausführbare SELECT-, INSERT-, UPDATE- und DELETE-Policies für mandantenfähige Tabellen erzeugen, ohne Boilerplate-SQL von Hand zu schreiben.

CREATE INDEX IF NOT EXISTS idx_workspace_members_lookup
  ON workspace_members (workspace_id, user_id);

ALTER TABLE documents ENABLE ROW LEVEL SECURITY;

CREATE POLICY "workspace_members_manage_documents"
ON documents FOR ALL TO authenticated
USING (
  workspace_id IN (
    SELECT wm.workspace_id FROM workspace_members wm
    WHERE wm.user_id = (SELECT auth.uid())
  )
)
WITH CHECK (
  workspace_id IN (
    SELECT wm.workspace_id FROM workspace_members wm
    WHERE wm.user_id = (SELECT auth.uid())
      AND wm.role IN ('owner', 'admin', 'member')
  )
);

Häufige Fragen zu RLS und Workspace-Auth

Teams, die mandantenfähiges Postgres verdrahten, stoßen bei Service-Rollen, Hintergrund-Jobs und Performance immer wieder auf dieselben Grenzfälle.

  • Umgehen Drizzle ORM oder Prisma RLS? — Ja, wenn sich dein Server mit dem postgres-Superuser oder dem Supabase-service_role-Key verbindet. Deshalb müssen Server Actions und API-Routen requireWorkspaceViewer() aufrufen und jede Abfrage nach workspaceId filtern.
  • Warum (select auth.uid()) in RLS-Policies in Klammern setzen? — So wertet der Postgres-Optimizer auth.uid() einmal pro Statement statt einmal pro gescannter Zeile aus und vermeidet langsame Sequential Scans auf großen Tabellen.
  • Wie sollten Hintergrund-Jobs auf Mandantendaten zugreifen? — Übergib die verifizierte workspaceId aus der authentifizierten Server-Route im Job-Payload und beschränke Lese- und Schreibzugriffe des Workers auf diese workspaceId.
  • Wie verifiziere ich, dass die Mandantenisolation wirklich hält? — Schreibe automatisierte Negativtests, in denen Nutzer A über Web-, Mobile- und API-Routen die Datensatz-ID von Nutzer B anfragt oder ändert, und prüfe auf 403 / 404 ohne geänderte Zeilen.

Häufige Fragen

14 Fragen

Was ist supastack?

supastack ist eine modulare Basis zum Bauen von KI-gestützten SaaS-Produkten. Sie bündelt die gemeinsame Infrastruktur, damit du dich auf die Funktionen konzentrieren kannst, die dein Produkt ausmachen.

Unterstützt es Web und Mobil?

Ja. supastack enthält Next.js- und Expo-Apps als Ausgangspunkt für Web- und Mobile-Erlebnisse.

Welche Authentifizierung ist enthalten?

Better Auth deckt E-Mail/Passwort, OTP und Social Login ab. Persönliche Workspaces werden mit dem Konto provisioniert, damit Datensätze vom ersten Tag an begrenzt sind.

Wie wird KI unterstützt?

supastack enthält konfigurierbare Multi-Anbieter-KI, Nutzungsverfolgung, Budgets und verschlüsselte Bring-your-own-key-Unterstützung.

Wie werden Abonnements gehandhabt?

Für Produkte, die auf dem Kit aufbauen, bilden Stripe-Abrechnung, Abonnements und Berechtigungen die Basis für bezahlten Zugang. Anbieter-Geheimnisse bleiben serverseitig.

Wo liegt mein produktspezifischer Code?

Produktspezifische Funktionen bleiben in einem separaten Codebase, während das Produkt die Basismodule von supastack konfiguriert und integriert.

Wie funktioniert die Preisgestaltung von supastack?

Das Kit ist ein Einmalkauf mit lebenslangen Updates: Single Stack für 249 $ — deine Wahl aus dem Next.js- (Web), Expo- (iOS & Android) oder Tauri-Repo (macOS & Windows Desktop) —, Dual Stack für 398 $ mit zwei beliebigen Repos oder Full Bundle für 399 $ mit allen drei Repos und dem vollständigen Monorepo, plus 75 $ pro zusätzlichem Entwickler-Sitz. Kein Kit-Abo. Checkout auf dieser Website liefert deinen Lizenzschlüssel per E-Mail, mit Repo-Zugang per GitHub-Einladung.

Wie begrenzen Workspaces meine Daten?

Nutzerdatensätze sind über workspaceId begrenzt. Identität, Berechtigungen, KI-Nutzung, Abrechnungsereignisse und Audit-Einträge bleiben innerhalb der persönlichen Workspace-Grenze.

Wie funktionieren Hintergrundjobs?

Trigger.dev ist die explizite Job-Laufzeit. Der JobRunner-Vertrag verspricht nur enqueue, status und cancel — Workflow-Semantik bleibt Trigger.dev-spezifisch und explizit.

Wie unterscheidet sich E-Mail von Outreach?

Transaktions-E-Mail deckt Produktabläufe wie Task-Abschluss-Hinweise ab. Cold Outreach und Kampagnen gehören zum Produkt selbst, außerhalb des E-Mail-Moduls dieses Kits.

Wie funktionieren mobile Käufe?

Mobile bietet native Käufe und Wiederherstellung über RevenueCat; dein Server prüft den Zugriff. Konfiguriere die Store-Konten und schließe Gerätetests vor der Veröffentlichung ab. Web-Abrechnung bleibt separat.

Wo liegen die KI-Anbieter-Schlüssel?

BYOK-Zugangsdaten sind verschlüsselt und bleiben serverseitig. Schlüssel werden in Logs bereinigt; Nutzung und Budgets bleiben auf Workspace-Ebene sichtbar.

Welche Zahlungsmethoden akzeptiert der Checkout?

Der von Stripe gehostete Checkout akzeptiert Karten und kann — sobald die Wallets im Stripe-Konto aktiviert sind — auch Alipay und WeChat Pay anbieten, praktisch für Käufer in China. Wallet-Autorisierungen werden asynchron bestätigt, sodass sich die Lizenzseite aktualisiert, sobald die Zahlung eingegangen ist.

Kann ich eine Rückerstattung erhalten?

Sobald du Zugriff auf das Repository hast, gehört supastack für immer dir und kann daher nicht zurückerstattet werden. Aber keine Sorge: Du erhältst den vollständigen Quellcode mit lebenslangen Updates, um unbegrenzt viele Produkte für dich oder deine Kunden zu erstellen und zu veröffentlichen. Du darfst den Code lediglich nicht weiterverkaufen oder das Repository als Starter-Kit oder Vorlage weitergeben. Eine Ausnahme ist der Launch-Cashback — launche ein Live-Produkt, das mit supastack gebaut ist, teile es und erreiche innerhalb von 60 Tagen 5.000 organische Aufrufe oder 50 organische Likes, um 50 % des gezahlten Betrags zurückzuerhalten; siehe die Bedingungen.

DE