Rollenbasierte Zugriffskontrolle in B2B-Marktplätzen richtig umsetzen

Rollenbasierte Zugriffskontrolle in B2B-Marktplätzen richtig umsetzen

Die Zugriffskontrolle auf einem B2B-Marktplatz geht über die klassische rollenbasierte Zugriffskontrolle (RBAC) in mandantenfähigen Systemen hinaus. Käufer und Lieferanten greifen auf bestimmte gemeinsame Datensätze zu, während andere Informationen ausschließlich für die jeweilige Organisation zugänglich bleiben müssen. Dabei gilt eine zentrale Regel: Niemand darf auf mehr Informationen zugreifen, als für die eigenen Aufgaben erforderlich ist.

In diesem Artikel zeigen wir, wie sich eine rollenbasierte Zugriffskontrolle (RBAC) für solche B2B-Marktplätze umsetzen lässt. Auf die allgemeinen Grundlagen von RBAC verzichten wir und konzentrieren uns stattdessen auf die spezifischen Anforderungen von Marktplattformen. Im Mittelpunkt stehen organisationsbezogene Rollen, die Speicherung von Berechtigungen, die Datenisolierung zwischen Mandanten, Genehmigungsprozesse für Bestellungen und die Durchsetzung von Zugriffsrechten im Backend.

Warum sich RBAC auf B2B-Marktplätzen unterscheidet

Bei Anwendungen für ein einzelnes Unternehmen lassen sich Rollen relativ einfach definieren. Ein Administrator ist ein Administrator, ein Betrachter ist ein Betrachter. Auf einem B2B-Marktplatz ist die Situation jedoch komplexer.

Betrachten wir folgendes Beispiel: Dana arbeitet als Einkaufsleiterin bei einem Käuferunternehmen. Gleichzeitig unterstützt sie das kleine Unternehmen eines Freundes, das auf derselben Plattform Produkte verkauft. Für beide Tätigkeiten verwendet sie dasselbe Benutzerkonto. Deshalb wäre es nicht sinnvoll, ihr eine einzige globale Rolle zuzuweisen. Stattdessen müssen Rollen an die Mitgliedschaft eines Benutzers in einer bestimmten Organisation gebunden sein.

Während das klassische RBAC-Modell Benutzer mit Rollen und Rollen mit Berechtigungen verknüpft, kommt hier die Organisationsebene hinzu. Jede Zugriffsprüfung muss daher zwei Fragen beantworten:

  • Verfügt der Benutzer über die erforderliche Berechtigung?
  • Gilt diese Berechtigung innerhalb der betreffenden Organisation?

Ein weiterer Unterschied betrifft die Sichtbarkeit von Daten, da der Informationsaustausch auf einem Marktplatz häufig über Unternehmensgrenzen hinweg erfolgt. Ein Käufer kann beispielsweise Produktkataloge und Angebote von Lieferanten einsehen, jedoch nicht deren interne Kostendaten. Ein Lieferant wiederum kann eingehende Bestellungen sehen, erhält aber keinen Zugriff auf interne Genehmigungsvermerke des Käufers.

Auch Mitarbeiter des Plattformbetreibers benötigen unter Umständen Zugriff, um Supportanfragen zu bearbeiten. Solche Zugriffe müssen jedoch protokolliert und mit einem ausdrücklich festgelegten Ablaufdatum versehen werden.

Darüber hinaus beeinflusst der Bearbeitungsstatus eines Datensatzes die Zugriffsrechte. Eine Bestellung im Status „Entwurf“ darf beispielsweise noch bearbeitet werden, während sie nach der Bestätigung nur noch lesbar sein sollte.

Organization-Level Access Control

Viele Teams versuchen, bereits von Anfang an zahlreiche Rollen zu definieren. Das ist wahrscheinlich ein Fehler, denn mit der Weiterentwicklung des Systems stellt sich häufig heraus, dass sich viele dieser Rollen überschneiden. Ein besserer Ansatz besteht darin, zunächst eine kleine Anzahl von Rollen pro Organisationstyp festzulegen. Eine Käuferorganisation kann beispielsweise mit fünf Rollen beginnen: einem Inhaber, der die Abrechnung und die Mitglieder verwaltet, einem Einkäufer, der Warenkörbe und Bestellungen erstellt, einem Freigeber, der Bestellungen oberhalb eines festgelegten Schwellenwerts genehmigt, und einem Betrachter, der Bestellungen und Rechnungen einsehen kann. Lieferantenorganisationen benötigen möglicherweise einen Inhaber, der Mitglieder und Auszahlungsdaten verwaltet, einen Katalogmanager, der Produkte und Preislisten bearbeitet, einen Vertriebsmitarbeiter, der Angebote und Angebotsanfragen (RFQs) beantwortet, eine Rolle für die Auftragsabwicklung, die den Versandstatus aktualisiert, sowie eine Finanzrolle, die Rechnungen und Auszahlungen einsehen kann.

Während sich die Rollenbezeichnungen je nach Organisationstyp unterscheiden können, sollten die Berechtigungsschlüssel einheitlich bleiben. Eine Rolle ist lediglich eine benannte Zusammenstellung dieser Schlüssel. Das folgende Beispiel zeigt eine Berechtigungsmatrix für Benutzer auf der Käuferseite eines Marktplatzes:

Berechtigung Einkäufer Freigeber Betrachter
order.create ja nein nein
order.approve nein ja nein
order.view ja ja ja
invoice.view nein ja ja

Rollen auf Plattformebene benötigen einen separaten Geltungsbereich, der typischerweise Supportmitarbeiter, Compliance-Prüfer und Plattformadministratoren umfasst. Diese Rollen sollten außerhalb der Tabelle für organisationsbezogene Rollen verwaltet werden.

Große Kunden benötigen möglicherweise individuelle Rollen. Diese sollten aus den bestehenden Berechtigungsschlüsseln zusammengestellt werden, anstatt neue zu erfinden. Ein Berechtigungsschlüssel stellt eine feste Vereinbarung mit dem Anwendungscode dar, die unabhängig von der Weiterentwicklung des Systems eingehalten werden muss.

Datenbankdesign

Das Team von Chudovo beginnt bei diesem Modell mit fünf Tabellen. Dabei wird org_id als Feld behandelt, das NULL-Werte zulässt: NULL steht für eine Systemrolle, die von allen Organisationen gemeinsam genutzt wird. Ein Wert in dieser Spalte bedeutet dagegen, dass es sich um eine benutzerdefinierte Rolle handelt, die einer bestimmten Organisation gehört. Die Einschränkung UNIQUE (user_id, org_id) verhindert doppelte Einträge für Organisationsmitgliedschaften.

Bei jeder Zuweisung einer benutzerdefinierten Rolle muss eine Validierung erfolgen. Dabei ist sicherzustellen, dass ihre org_id mit der Organisation der jeweiligen Mitgliedschaft übereinstimmt. Gemeinsam genutzte Rollen müssen außerdem zum Organisationstyp passen, also entweder Käufer oder Lieferant. Alle diese Prüfungen sollten in der Serviceschicht erfolgen, da die oben genannten Fremdschlüssel keine dieser Regeln durchsetzen. Ebenso wichtig ist die Prüfung, welche Rollen der handelnde Administrator vergeben darf, um eine unzulässige Ausweitung von Berechtigungen zu verhindern.

Berechtigungsschlüssel sollten in role_permissions als einfache Zeichenfolgen gespeichert werden. Eine separate Berechtigungstabelle sorgt zwar für eine übersichtliche Struktur, erfordert aber zusätzlich einen Join und einen Migrationsschritt, ohne einen nennenswerten Mehrwert zu bieten.

Das folgende Schema verwendet die PostgreSQL-Syntax:

CREATE TABLE organizations (
id uuid PRIMARY KEY,
type text NOT NULL CHECK (type IN ('buyer', 'supplier')),
name text NOT NULL
);
​
CREATE TABLE memberships (
id uuid PRIMARY KEY,
user_id uuid NOT NULL REFERENCES users(id),
org_id uuid NOT NULL REFERENCES organizations(id),
status text NOT NULL DEFAULT 'active',
UNIQUE (user_id, org_id)
);
​
CREATE TABLE roles (
id uuid PRIMARY KEY,
org_id uuid REFERENCES organizations(id), -- NULL = system role
name text NOT NULL
);
​
CREATE TABLE role_permissions (
role_id uuid REFERENCES roles(id),
permission text NOT NULL,
PRIMARY KEY (role_id, permission)
);
​
CREATE TABLE membership_roles (
membership_id uuid REFERENCES memberships(id),
role_id uuid REFERENCES roles(id),
PRIMARY KEY (membership_id, role_id)
);

Datenisolierung in mandantenfähigen Systemen

Bei der Verwaltung von Mandantendaten stehen Entwicklungsteams verschiedene Ansätze zur Verfügung: gemeinsame Tabellen mit einer Organisationsspalte, ein eigenes Schema pro Mandant oder eine separate Datenbank für jeden Mandanten.

Betrachten wir zunächst die letzten beiden Optionen aus praktischer Sicht: Eine Bestellung kann zwei Eigentümer haben, sodass sowohl die Käuferorganisation als auch die Lieferantenorganisation darauf zugreifen können müssen. Bei einem separaten Schema pro Mandant befindet sich der Datensatz in einem Schema, muss aber auch aus einem anderen Schema zugänglich sein. Das erschwert die Umsetzung erheblich. Die dritte Option ist noch problematischer. Wenn bereits ein Datensatz in einem anderen Schema große Schwierigkeiten verursacht, bringt ein Datensatz in einer vollständig separaten Datenbank noch deutlich größere Herausforderungen für die Infrastruktur und Entwicklung mit sich.

Damit sind gemeinsame Tabellen die sinnvollste Wahl. Bei diesem Ansatz enthält jeder gemeinsam genutzte Datensatz sowohl eine buyer_org_id als auch eine supplier_org_id. Die Organisation des anfragenden Benutzers wird bei jeder Datenbankabfrage als Filter verwendet.

Für mandantenfähige Anwendungen kann zusätzlich die Row-Level Security (RLS) von PostgreSQL eingesetzt werden. Dadurch gibt eine Abfrage nur die Datensätze zurück, die gemäß den festgelegten Richtlinien zugänglich sind, ohne dass die Abfrage selbst spezielle Organisationsfilter enthalten muss. Der Nachteil besteht darin, dass sich Abfragen schwieriger debuggen lassen. Deshalb sollte dieser Ansatz vor der eigentlichen Implementierung anhand der Daten des jeweiligen Projekts geprüft werden. Außerdem ist zu beachten, dass diese Richtlinien nur die Leseberechtigungen regeln. Um einzuschränken, welche Schreiboperationen eine Organisation durchführen darf, müssen separate Richtlinien definiert werden.

Jede Transaktion sollte app.org_id mit SET LOCAL setzen, damit die Einstellungen eines Mandanten nicht auf einen anderen übertragen werden. Bei gemeinsam genutzten Datenbankverbindungen kann ein einfaches SET dazu führen, dass die Mandanteneinstellung einer Anfrage bei der nächsten Anfrage bestehen bleibt. Gleichzeitig sollte man sich nicht darauf verlassen, dass das Frontend bestimmte Felder ausblendet. Das Frontend kann zwar eigene Regeln dafür verwenden, welche Informationen angezeigt werden, diese dürfen jedoch nicht als verbindliche Zugriffskontrolle gelten. Besser ist es, unterschiedliche Datenansichten über Serializer oder Datenbank-Views bereitzustellen, mit jeweils einer eigenen Antwortstruktur für Käufer und Lieferanten.

Genehmigungsprozesse für Bestellungen

Nehmen wir an, wir haben eine Berechtigung eingerichtet, nach der ein Einkäufer Bestellungen einreichen darf, wobei für Bestellungen über 5.000 US-Dollar eine Genehmigung erforderlich ist. Sobald ein Einkäufer eine Bestellung erstellt, die diesen Schwellenwert überschreitet, wechselt die Bestellung in den Status pending_approval. Ein Freigeber prüft sie, und erst danach wird die Bestellung an den Lieferanten weitergeleitet.

Genehmigungsprozess für Bestellungen auf B2B-Marktplätzen

Schwellenwerte gehören zu den Daten und nicht zu den Rollen. Eine Tabelle approval_policies enthält die Felder org_id, requires_approval_above, max_approval_amount, currency und approver_role_id. Der erste Betrag bestimmt, ob eine Genehmigung erforderlich ist. Der zweite legt den Höchstbetrag fest, den die jeweilige Rolle genehmigen darf. Die Währung dient dazu, die Beträge für den Vergleich zu vereinheitlichen. Die Beträge sollten nicht zur Benennung von Rollen verwendet werden, etwa „Freigeber bis 5.000“ oder „Freigeber bis 20.000“, da dies innerhalb eines Jahres zu einer unüberschaubaren Anzahl von Rollen führen kann. Grundsätzlich sollte eine Funktionstrennung durchgesetzt werden, sodass die Person, die eine Bestellung erstellt hat, diese nicht selbst genehmigen darf. Für Organisationen mit nur einem Benutzer kann eine separate, ausdrücklich definierte Ausnahmeregelung erforderlich sein.

Backend-Prüfungen für die Zugriffskontrolle auf B2B-Marktplätzen

Controller und UI-Code können Zugriffsprüfungen enthalten, doch die eigentliche Durchsetzung der Berechtigungen sollte in der Serviceschicht erfolgen, da diese bei jeder Anfrage durchlaufen wird. Das Team von Chudovo implementiert in der Regel eine Autorisierungsmethode, die zunächst prüft, ob für die angegebene Kombination aus Benutzer- und Organisations-ID eine Mitgliedschaft existiert. Wird keine passende Mitgliedschaft gefunden, wird die Verarbeitung sofort mit einer Ausnahme abgebrochen. Andernfalls prüft das System die Berechtigungen dieser Mitgliedschaft und vergleicht sie mit der Berechtigung, die für den Zugriff auf die jeweilige Ressource erforderlich ist. Darf auf die Ressource nicht zugegriffen werden, gibt das System einen 404-Fehler zurück (anstelle von 401/403, wodurch die Existenz des Datensatzes bestätigt würde).

Die oben beschriebene Methode kann aufgerufen werden, wenn das System eine Aktion ausführen muss, beispielsweise bei der Genehmigung einer Bestellung und vor jedem Statuswechsel.

Das Lesen der Bestellung, die Genehmigungsprüfungen und der Statuswechsel sollten innerhalb derselben Datenbanktransaktion erfolgen. Damit sich eine Bestellung zwischen der Prüfung und der Genehmigung nicht ändern kann, sollte sie vor dem Lesen gesperrt werden. Dasselbe gilt für Änderungen an Bestellsummen oder einzelnen Bestellpositionen.

Bei Benutzern, die mehreren Organisationen angehören, übermittelt der Client normalerweise die ID der aktuell ausgewählten Organisation. Diesem Wert darf nicht ohne Weiteres vertraut werden. Stattdessen muss er bei jeder Anfrage serverseitig anhand der Organisationsmitgliedschaften des Benutzers überprüft werden. Die ID sollte nicht aus dem Request-Body gelesen werden. Besser ist es, sie aus der Sitzung zu übernehmen.

JWTs mit langer Gültigkeitsdauer sollten keine Berechtigungen enthalten, da ein entferntes Mitglied andernfalls bis zum Ablauf des Tokens weiterhin Zugriff behält. Bei einem unzulässigen Zugriff auf Daten eines anderen Mandanten sollte das System einen 404-Fehler statt eines 403-Fehlers zurückgeben. Letzterer verrät Angreifern, dass die Ressource existiert, und erleichtert ihnen das systematische Ermitteln von Bestellnummern.

Berechtigungen können für kurze Zeit zwischengespeichert werden. Der Cache muss jedoch ungültig gemacht werden, sobald sich die aktive Rolle ändert.

Die oben beschriebenen Maßnahmen gelten auch für Hintergrundprozesse. Andernfalls könnten diese dazu genutzt werden, die RBAC-Zugriffskontrolle zu umgehen.

Häufige Sicherheitsfehler

Im Code verschiedener Marktplatzprojekte treten häufig ähnliche Fehler auf. Einer der wichtigsten besteht darin, sich auf die Benutzeroberfläche zu verlassen, anstatt geeignete Prüfungen auf der Serverseite zu implementieren. Ein bereits erwähntes Beispiel ist die Übernahme von org_id aus dem Request-Body oder einem Query-Parameter, anstatt diese Information aus der Sitzung zu beziehen. Doch das ist nicht der einzige Fehler. Die Benutzeroberfläche enthält häufig eine Anzeigelogik, die abhängig von einem bestimmten Zustand Schaltflächen, Filter oder Informationen ausblendet. Manche Teams verlassen sich auf diese Logik als Sicherheitsmechanismus. Ein sicheres System muss seine Sicherheitsrichtlinien jedoch unabhängig davon im Backend durchsetzen, wie die Benutzeroberfläche dargestellt wird.

Ein weiterer häufiger Fehler besteht darin, nur Bestellungen zu schützen und dabei untergeordnete Objekte zu vergessen. Stellen wir uns vor, ein Team sichert GET /orders/:id sorgfältig ab, stellt aber gleichzeitig GET /attachments/:id bereit. Dieser Endpunkt benötigt lediglich eine Anhang-ID, ohne dass bei jeder Anfrage eine Zugriffsprüfung erfolgt. OWASP bezeichnet dies als Broken Object Level Authorization (BOLA). Diese Schwachstelle steht an erster Stelle der OWASP API Security Top 10. Wie lässt sich das Problem beheben? Jede Abfrage eines Objekts sollte über eine zentrale Funktion erfolgen, die die Organisation des anfragenden Benutzers berücksichtigt. Direkte findById-Aufrufe in Handlern sollten untersagt werden.

Weitere häufige Fehler sind:

  • die Speicherung einer einzigen globalen Rolle im Benutzerdatensatz
  • API-Schlüssel und Webhooks ohne Einschränkung auf eine bestimmte Organisation, sodass ein einzelner Schlüssel auf die Daten aller Organisationen zugreifen kann
  • dauerhafte Zugriffsrechte für Supportmitarbeiter ohne nachvollziehbare Protokollierung

Oft erkennen Entwicklungsteams diese Probleme erst, nachdem der Code bereits einige Zeit produktiv im Einsatz ist.

Wo sollte man anfangen?

Wer sich fragt, wie sich eine rollenbasierte Zugriffskontrolle auf B2B-Marktplätzen implementieren lässt, sollte am besten schrittweise vorgehen. Beginnen Sie mit einer kleinen Lösung aus fünf Tabellen und einer zentralen Autorisierungsfunktion. In den meisten Fällen reicht das bereits für viele Anforderungen aus. Falls nicht, lässt sich das System entsprechend den spezifischen Anforderungen erweitern.

In jedem Fall müssen geeignete Tests von Anfang an berücksichtigt werden. Besonders wichtig sind mandantenübergreifende Tests. Sie lassen sich einfach durchführen: Rufen Sie jeden Endpunkt mit dem Token einer nicht zugehörigen Organisation auf und versuchen Sie damit, auf die Daten einer anderen Organisation zuzugreifen. Als Antwort sollte ein 404-Fehler zurückgegeben werden. Denken Sie außerdem daran, die Berechtigungen von Käufern und Lieferanten getrennt zu testen. Nur weil beide auf dieselbe Bestellung zugreifen können, bedeutet das nicht, dass sie auch dieselben Aktionen ausführen dürfen.

Wir verwenden technisch notwendige Cookies, um die einwandfreie Funktion unserer Website sicherzustellen. Mit Ihrer Einwilligung verwenden wir außerdem optionale Cookies für Analysezwecke und weitere Funktionen. Sie können alle Cookies akzeptieren, optionale Cookies ablehnen oder Ihre Einstellungen individuell anpassen. Weitere Informationen finden Sie in unserer Cookie-Richtlinie.