Besitz serverseitig ableiten
Eine Datensatz-ID identifiziert etwas; sie beweist nicht, wer es lesen darf. supastack begrenzt Nutzerdatensätze über workspaceId. Der Server löst den Workspace des authentifizierten Nutzers auf und nimmt diese Grenze auf, wenn ein Datensatz gelesen oder geändert wird.
Behandle eine vom Browser gesendete Workspace-ID nicht als Besitznachweis. Der Browser kann eine Aktion anfragen, aber der vertrauenswürdige Server-Kontext muss feststellen, wessen Aktion es ist. Diese Regel muss für Task-Status, gespeicherte Anbieter-Konfiguration, Nutzungsdatensätze und jede produkteigene Ressource gelten. Layouts und Komponenten können filtern, was rendert, aber Rendern ist keine Autorisierung — die Prüfung gehört dorthin, wo der Datensatz tatsächlich gelesen oder geschrieben wird.
Alles Weitere in diesem Post folgt aus dieser Regel: Besitz wird abgeleitet, niemals übergeben. In dem Moment, in dem ein vom Client gesendetes workspaceId irgendwo vertraut wird, wird jede andere Prüfung zur Dekoration. Der Authentifizierungs-Post behandelt, wie die Session selbst vor dieser Grenze hergestellt wird.
Mitgliedschaft ist Teil der Grenze
Scoping beantwortet, zu welchem Workspace ein Datensatz gehört; Mitgliedschaft beantwortet, ob dieser Nutzer zu jenem Workspace gehört. Löse beides serverseitig auf. supastack modelliert persönliche und Organisations-Workspaces mit Besitzer-, Admin- und Mitgliederrollen — der Workspaces-Guide dokumentiert das Modell.
Rollen fügen eine zweite Achse hinzu: Ein Mitglied darf lesen, was nur ein Admin ändern darf. Halte die beiden Prüfungen getrennt — „gehört zu diesem Workspace“ und „darf diese Aktion ausführen“ —, damit eine Herabstufung oder Entfernung an derselben Grenze greift wie alles andere. Einladungen und Entfernungen sind Teil desselben Lifecycles, kein nachträglicher Admin-Gedanke.
Mitgliedschaftsänderungen müssen außerdem sofort greifen. Die nächste Anfrage eines entfernten Mitglieds sollte an der Grenze scheitern, nicht erst, wenn ein gecachtes Token zufällig abläuft — ein weiterer Grund, warum die Prüfung dort lebt, wo Datensätze gelesen werden, und nicht in einem früher ausgestellten Credential.
Wiederherstellung nützlich machen, ohne Zugang zu gewähren
Eine Task-URL lässt einen Nutzer nach einem Refresh zurückkehren. Sie ist nützlich, gerade weil sie eine Referenz auf die Arbeit hält. Die authentifizierte API muss trotzdem prüfen, dass der angefragte Task zum aktuellen Workspace gehört, bevor sie Status oder Ergebnisse zurückgibt.
Dasselbe Prinzip gilt für die Wiederherstellung von Einreichungen. Ein Idempotenzschlüssel hilft, eine wiederholte Anfrage zu erkennen und doppelte Arbeit zu verhindern. Er ist weder eine Session noch ein Zugangstoken. Das Wiederholen einer gespeicherten Einreichung erfordert weiterhin Authentifizierung, Workspace-Besitz und die passende Berechtigung.
Bequemlichkeitspfade sind die Stellen, an denen Isolation still erodiert. Ein Link, der einem berechtigten Nutzer hilft, Arbeit wiederaufzunehmen — nach einem Refresh, aus einer Abschluss-E-Mail —, muss dieselbe authentifizierte Prüfung durchlaufen wie jede andere Anfrage. Die Request-to-Result-Pipeline nutzt task-bezogene Idempotenzschlüssel genau nach diesem Prinzip.
Die Anfragen testen, die fehlschlagen müssen
Ein Test, der einen Besitzer einen Datensatz abrufen lässt, beweist den Happy Path. Er beweist keine Isolation. Kombiniere ihn mit einem zweiten Workspace, der denselben Datensatz anfragt, einer anonymen Anfrage und einer Schreiboperation, die versucht, einen anderen Besitzer zu unterschieben.
Prüfe das externe Verhalten ebenso wie das persistierte Ergebnis. Eine abgelehnte Anfrage darf die Daten eines anderen Workspaces nicht verändern, und eine Fehlerantwort darf weder Geheimnisse noch rohe Datenbankdetails preisgeben. Diese Prüfungen halten die Grenze reviewbar, wenn sich die Implementierung ändert.
- Ein zweiter Workspace fragt dieselbe Datensatz-ID an — abgelehnt.
- Eine anonyme Anfrage trifft denselben Endpoint — abgelehnt.
- Ein aus dem Workspace entferntes Mitglied versucht es erneut — abgelehnt.
- Ein Write nennt einen anderen Besitzer oder Workspace — abgelehnt, ohne dass etwas verändert wird.
- Fehlerantworten bleiben generisch — keine Geheimnisse, keine Schema-Details.
Die Regel in dein Produkt tragen
Wenn du eine neue Produkt-Tabelle hinzufügst, entscheide ihren Besitz, bevor du den Screen baust. Nimm workspaceId in ihr Persistenzmodell auf, löse den Workspace über die bestehende Server-Grenze auf und nutze diesen Kontext in Listen- wie Detailoperationen.
Hintergrund-Worker verlangen dieselbe Sorgfalt. Ein eingereihter Task muss genug vertrauenswürdigen Kontext tragen, um den richtigen Datensatz zu laden, während Anbieter-Zugangsdaten auf dem Server bleiben. Auch ein geplanter Job, der Zeilen anhand nackter IDs lädt, liegt innerhalb der Grenze. Eine konsistente Besitzregel ist leichter zu warten als eine Sammlung routenspezifischer Ausnahmen.
Die API-Oberfläche gehört zur selben Grenze. Workspace-API-Schlüssel und signierte ausgehende Webhooks tragen den Scope ihres Workspaces mit sich — ein Schlüssel darf nie zu einem Weg werden, mandantenübergreifend zu lesen, und ein Webhook-Payload darf nie die Datensätze eines Nachbarn tragen. Der API-Modul-Guide beschreibt die Form dieser Oberfläche.
Häufige Fragen zur Workspace-Isolation
Multi-Tenant-Isolation wirft in jedem Projekt dieselbe Handvoll Fragen auf. Diese hier lohnt es sich früh zu klären.
- Reicht Scoping auf Anwendungsebene, oder brauche ich Row-Level Security? — Serverseitig erzwungenes Scoping bei jedem Lesen und Schreiben deckt das Bedrohungsmodell der meisten SaaS ab. Datenbank-RLS fügt Tiefenverteidigung hinzu, ersetzt aber keine Grenzprüfungen — Worker und API-Schlüssel brauchen weiterhin dieselbe Regel.
- Wie sollte Teilen über Workspaces hinweg funktionieren? — Modelliere es explizit — eine Einladung, ein Mitgliedschaftsdatensatz —, statt den Scope aufzuweichen. „Jeder mit dem Link“ sollte ein gestaltetes Feature mit eigenen Prüfungen sein, kein Unfall.
- Was bricht in Multi-Tenant-Apps zuerst? — Bequemlichkeitspfade: Wiederherstellungs-Links, Admin-Tooling, Hintergrund-Worker, die Datensätze anhand nackter IDs laden. Auditiere jeden Codepfad, der einen Datensatz berührt, ohne den normalen Scope zu durchlaufen.
- Wie füge ich einer bestehenden Tabelle Scoping hinzu? — Ergänze
workspaceId, fülle die Werte nach, setze es in Reads und Writes durch und fass danach das UI an. Liefere die negativen Tests zuerst, damit die Grenze bewiesen statt angenommen ist. - Brauchen Server-Actions Prüfungen, wenn die Seite geschützt ist? — Ja. Page-Guards verstecken Screens; sie autorisieren keine Operationen. Jede Action löst Session und Workspace unabhängig auf.