Leid eigenaarschap af op de server
Een record-ID identificeert iets; het bewijst niet wie het mag lezen. supastack bakt records van gebruikers af per workspaceId. De server resolveert de workspace van de geauthenticeerde gebruiker en neemt die scope mee bij het lezen of wijzigen van een record.
Behandel een door de browser gestuurd workspace-ID niet als bewijs van eigenaarschap. De browser kan een actie aanvragen, maar de vertrouwde servercontext moet vaststellen wiens actie het is. Deze regel moet gelden voor taakstatus, opgeslagen providerconfiguratie, usagerecords en elke product-owned resource. Layouts en componenten kunnen filteren wat rendert, maar renderen is geen autorisatie — de check hoort waar het record echt wordt gelezen of geschreven.
Alles verder in deze post volgt uit die regel: eigenaarschap wordt afgeleid, nooit opgevraagd. Zodra een door de client gestuurde workspaceId ergens wordt vertrouwd, wordt elke andere check decoratie. De authenticatiepost dekt hoe de sessie zelf upstream van deze grens wordt vastgesteld.
Lidmaatschap is deel van de grens
Scoping beantwoordt tot welke workspace een record behoort; lidmaatschap beantwoordt of deze gebruiker bij die workspace hoort. Resolveer beide op de server. supastack modelleert persoonlijke en organisatieworkspaces met owner-, admin- en member-rollen — de workspacesgids documenteert het model.
Rollen voegen een tweede as toe: een member mag lezen wat alleen een admin mag wijzigen. Houd de twee checks gescheiden — “behoort tot deze workspace” en “mag deze actie uitvoeren” — zodat een degradatie of verwijdering op dezelfde grens ingaat als alles anders. Uitnodigingen en verwijderingen zijn deel van dezelfde lifecycle, geen admin-bijzaak.
Lidmaatschapswijzigingen moeten ook onmiddellijk ingaan. De volgende request van een verwijderd lid moet op de grens falen, niet wanneer een gecachte token toevallig verloopt — nog een reden dat de check leeft waar records worden gelezen, in plaats van in een eerder uitgegeven credential.
Maak recovery nuttig zonder toegang te verlenen
Een taak-URL laat een gebruiker terugkeren na een refresh. Hij is nuttig juist omdat hij een referentie naar het werk bewaart. De geauthenticeerde API moet nog steeds verifiëren dat de gevraagde taak bij de huidige workspace hoort voordat status of resultaten worden teruggegeven.
Hetzelfde principe geldt voor submission-recovery. Een idempotencykey helpt een herhaalde request te identificeren en dubbel werk te voorkomen. Het is noch een sessie noch een toegangstoken. Een opgeslagen submission opnieuw proberen vereist nog steeds authenticatie, workspace-eigenaarschap en de juiste entitlement.
Gemakspaden zijn waar isolatie stilletjes erodeert. Een link die een legitieme gebruiker helpt werk te hervatten — na een refresh, vanuit een voltooiingsmail — moet door dezelfde geauthenticeerde check als elke andere request. De request-to-result-pipeline gebruikt taak-scoped idempotencykeys op precies dit principe.
Test de requests die moeten falen
Een test die een eigenaar een record laat ophalen bewijst het happy path. Het bewijst geen isolatie. Koppel het aan een tweede workspace die hetzelfde record aanvraagt, een anonieme request en een write die een andere eigenaar probeert te substitueren.
Assert ook het externe gedrag naast het gepersisteerde resultaat. Een afgewezen request mag de data van een andere workspace niet muteren, en een errorresponse mag geen secrets of rauwe databasedetails lekken. Deze checks maken de grens reviewbaar wanneer de implementatie verandert.
- Een tweede workspace vraagt hetzelfde record-ID aan — afgewezen.
- Een anonieme request raakt hetzelfde endpoint — afgewezen.
- Een uit de workspace verwijderd lid probeert opnieuw — afgewezen.
- Een write noemt een andere eigenaar of workspace — afgewezen, zonder dat iets gemuteerd is.
- Errorresponses blijven generiek — geen secrets, geen schemadetails.
Draag de regel over naar je product
Wanneer je een nieuwe producttabel toevoegt, bepaal dan het eigenaarschap ervan voordat je het scherm bouwt. Neem workspaceId op in het persistentiemodel, resolveer de workspace via de bestaande servergrens en gebruik die context in zowel list- als detailoperaties.
Achtergrondworkers hebben dezelfde zorg nodig. Een queued task moet genoeg vertrouwde context dragen om het juiste record te laden, terwijl providercredentials op de server blijven. Een geplande job die rijen laadt op blote ID zit nog steeds binnen de grens. Een consistente ownership-regel is makkelijker te onderhouden dan een verzameling route-specifieke uitzonderingen.
Het API-oppervlak hoort bij dezelfde grens. Workspace-API-keys en ondertekende uitgaande webhooks dragen de scope van de workspace met zich mee — een key mag nooit een manier worden om over tenants heen te lezen, en een webhookpayload mag nooit de records van een buur dragen. De API-modulegids dekt de vorm van dat oppervlak.
Veelgestelde vragen over workspace-isolatie
Multi-tenantisolatie roept op elk project dezelfde handvol vragen op. Dit zijn degene die je vroeg moet beslechten.
- Is scoping op applicatieniveau genoeg, of heb ik row-level security nodig? — Server-afgedwongen scoping op elke read en write dekt het threat model voor de meeste SaaS. Database-RLS voegt verdediging in de diepte toe maar vervangt grenschecks niet — workers en API-keys hebben nog steeds dezelfde regel nodig.
- Hoe moet delen over workspaces heen werken? — Modelleer het expliciet — een uitnodiging, een lidmaatschapsrij — in plaats van de scope te verzwakken. “Iedereen met de link” hoort een ontworpen feature met eigen checks te zijn, geen ongeluk.
- Wat breekt er eerst in multi-tenant-apps? — Gemakspaden: recoverylinks, admintooling, achtergrondworkers die records op blote ID laden. Audit elk codepad dat een record aanraakt zonder door de normale scope te gaan.
- Hoe voeg ik scoping toe aan een bestaande tabel? — Voeg
workspaceIdtoe, backfill die, handhaaf in reads en writes en raak daarna pas de UI. Ship eerst de negatieve tests zodat de grens bewezen is in plaats van aangenomen. - Hebben serveracties checks nodig als de pagina beschermd is? — Ja. Page guards verbergen schermen; ze autoriseren geen operaties. Elke actie resolveert de sessie en workspace onafhankelijk.