Zum Hauptinhalt springen

RBAC in der Praxis: Sichere Autorisierung für deine Anwendung implementieren

Hast du Schwierigkeiten, ein sicheres und skalierbares Autorisierungssystem für deine Anwendung zu implementieren? Rollenbasierte Zugangskontrolle (RBAC) ist der Industriestandard zur Verwaltung von Benutzerberechtigungen, aber die korrekte Umsetzung kann herausfordernd sein. In diesem Tutorial zeigen wir dir, wie du ein robustes RBAC-System anhand eines praxisnahen Content Management Systems (CMS) aufbaust.

Wenn du dieser Anleitung folgst, lernst du:

  • ✨ Wie du fein abgestufte Berechtigungen entwirfst und implementierst, um präzise Kontrolle zu erhalten
  • 🔒 Best Practices zur Organisation von Berechtigungen in sinnvolle Rollen
  • 👤 Techniken für effektives Ressourcen-Eigentum
  • 🚀 Möglichkeiten, dein Autorisierungssystem skalierbar und wartbar zu gestalten
  • 💡 Praktische Umsetzung anhand eines realen CMS-Beispiels

Der vollständige Quellcode zu diesem Tutorial ist auf GitHub verfügbar.

Grundprinzipien von RBAC verstehen

Rollenbasierte Zugangskontrolle ist mehr als nur das Zuweisen von Berechtigungen an Benutzer. Es geht darum, einen strukturierten Ansatz für Autorisierung zu schaffen, der Sicherheit und Wartbarkeit ausbalanciert.

Mehr dazu findest du unter Was ist RBAC im Auth Wiki.

Hier sind die wichtigsten Prinzipien, denen wir bei unserer Umsetzung folgen:

Fein abgestufte Berechtigungs-Entwürfe

Fein abgestufte Berechtigungen geben dir präzise Kontrolle darüber, was Benutzer in deinem System tun dürfen. Anstatt grober Zugriffsebenen wie „admin“ oder „user“ definieren wir spezifische Aktionen, die Benutzer auf Ressourcen ausführen können. Zum Beispiel:

  • read:articles – Beliebigen Artikel im System ansehen
  • create:articles – Neue Artikel erstellen
  • update:articles – Bestehende Artikel bearbeiten
  • publish:articles – Veröffentlichungsstatus von Artikeln ändern

Ressourcen-Eigentum und Zugangskontrolle

Ressourcen-Eigentum ist ein grundlegendes Konzept im Autorisierungsdesign unseres CMS. Während RBAC festlegt, welche Aktionen verschiedene Rollen ausführen dürfen, fügt Eigentum eine persönliche Dimension zur Zugangskontrolle hinzu:

  • Autoren haben automatisch Zugriff auf Artikel, die sie erstellt haben
  • Dieses natürliche Eigentumsmodell bedeutet, dass Autoren ihre eigenen Inhalte immer ansehen und bearbeiten können
  • Das System prüft sowohl Rollenberechtigungen ODER Eigentum bei der Bearbeitung von Artikeln
  • Zum Beispiel kann ein Autor auch ohne die Berechtigung update:articles seine eigenen Artikel bearbeiten
  • Dieses Design reduziert den Bedarf an zusätzlichen Rollenberechtigungen und erhält dennoch die Sicherheit

Dieser zweistufige Ansatz (Rollen + Eigentum) schafft ein intuitiveres und sichereres System. Publisher und Admins können weiterhin alle Inhalte über ihre Rollenberechtigungen verwalten, während Autoren die Kontrolle über ihre eigenen Werke behalten.

Sichere APIs entwerfen

Beginnen wir damit, die Kernfunktionalität unseres CMS über seine API-Endpunkte zu entwerfen:

GET    /api/articles         # Alle Artikel auflisten
GET /api/articles/:id # Einen bestimmten Artikel abrufen
POST /api/articles # Einen neuen Artikel erstellen
PATCH /api/articles/:id # Einen Artikel aktualisieren
DELETE /api/articles/:id # Einen Artikel löschen
PATCH /api/articles/:id/published # Veröffentlichungsstatus ändern

Zugangskontrolle für deine API umsetzen

Für jeden Endpunkt müssen wir zwei Aspekte der Zugangskontrolle berücksichtigen:

  1. Ressourcen-Eigentum – Gehört dem Benutzer diese Ressource?
  2. Rollenbasierte Berechtigungen – Erlaubt die Rolle des Benutzers diese Operation?

So handhaben wir den Zugriff für jeden Endpunkt:

EndpointZugangskontroll-Logik
GET /api/articles– Jeder mit list:articles-Berechtigung ODER Autoren sehen ihre eigenen Artikel
GET /api/articles/:id– Jeder mit read:articles-Berechtigung ODER Autor des Artikels
POST /api/articles– Jeder mit create:articles-Berechtigung
PATCH /api/articles/:id– Jeder mit update:articles-Berechtigung ODER Autor des Artikels
DELETE /api/articles/:id– Jeder mit delete:articles-Berechtigung ODER Autor des Artikels
PATCH /api/articles/:id/published– Nur Benutzer mit publish:articles-Berechtigung

Ein skalierbares Berechtigungssystem erstellen

Basierend auf unseren API-Zugriffsanforderungen können wir diese Berechtigungen definieren:

BerechtigungBeschreibung
list:articlesDie Liste aller Artikel im System ansehen
read:articlesDen vollständigen Inhalt beliebiger Artikel lesen
create:articlesNeue Artikel erstellen
update:articlesBeliebige Artikel bearbeiten
delete:articlesBeliebige Artikel löschen
publish:articlesVeröffentlichungsstatus ändern

Beachte, dass diese Berechtigungen nur benötigt werden, wenn du auf Ressourcen zugreifst, die dir nicht gehören. Artikelbesitzer können automatisch:

  • Ihre eigenen Artikel ansehen (kein read:articles nötig)
  • Ihre eigenen Artikel bearbeiten (kein update:articles nötig)
  • Ihre eigenen Artikel löschen (kein delete:articles nötig)

Effektive Rollen erstellen

Jetzt, da wir unsere API und Berechtigungen definiert haben, können wir Rollen erstellen, die diese Berechtigungen logisch gruppieren:

Berechtigung/Rolle👑 Admin📝 Publisher✍️ Autor
BeschreibungVoller Systemzugriff für komplette InhaltsverwaltungKann alle Artikel ansehen und Veröffentlichungen steuernKann neue Artikel im System erstellen
list:articles
read:articles
create:articles
update:articles
delete:articles
publish:articles

Hinweis: Autoren haben automatisch Lese-/Bearbeitungs-/Löschrechte für ihre eigenen Artikel, unabhängig von den Rollenberechtigungen.

Jede Rolle ist mit bestimmten Verantwortlichkeiten konzipiert:

  • Admin: Hat vollständige Kontrolle über das CMS, einschließlich aller Artikeloperationen
  • Publisher: Konzentriert sich auf Inhaltsprüfung und Veröffentlichungsmanagement
  • Autor: Spezialisiert auf Inhaltserstellung

Diese Rollenstruktur schafft eine klare Trennung der Zuständigkeiten:

  • Autoren konzentrieren sich auf die Inhaltserstellung
  • Publisher verwalten Inhaltsqualität und Sichtbarkeit
  • Admins behalten die Kontrolle über das Gesamtsystem

RBAC in Logto konfigurieren

Bevor du beginnst, musst du ein Konto in Logto Cloud erstellen oder du kannst auch eine selbst gehostete Logto-Instanz mit der Logto OSS-Version verwenden.

Für dieses Tutorial nutzen wir jedoch Logto Cloud der Einfachheit halber.

Deine Anwendung einrichten

  1. Gehe in der Logto Console zu „Applications“, um eine neue React-Anwendung zu erstellen

CMS React application

API-Ressourcen und Berechtigungen konfigurieren

  1. Gehe in der Logto Console zu „API Resources“, um eine neue API-Ressource zu erstellen
    • API-Name: CMS API
    • API-Identifier: https://api.cms.com
    • Füge der API-Ressource Berechtigungen hinzu
      • list:articles
      • read:articles
      • create:articles
      • update:articles
      • publish:articles
      • delete:articles

CMS API resource details

Rollen erstellen

Gehe zu Rollen in der Logto Console, um die folgenden Rollen für das CMS zu erstellen

  • Admin
    • mit allen Berechtigungen
  • Publisher
    • mit read:articles, list:articles, publish:articles
  • Autor
    • mit create:articles

Admin role

Publisher role

Author role

Rollen Benutzern zuweisen

Gehe zum Bereich „User management“ in der Logto Console, um Benutzer zu erstellen.

Im Tab „Roles“ der Benutzerdetails kannst du dem Benutzer Rollen zuweisen.

In unserem Beispiel erstellen wir 3 Benutzer mit folgenden Rollen:

  • Alex: Admin
  • Bob: Publisher
  • Charlie: Autor

User management

User details - Alex

hinweis:

Zu Demonstrationszwecken erstellen wir diese Ressourcen und Konfigurationen über die Logto Console. In realen Projekten kannst du diese Ressourcen und Konfigurationen programmatisch mit der von Logto bereitgestellten Management API erstellen.

Dein Frontend mit Logto RBAC integrieren

Jetzt, da wir RBAC in Logto eingerichtet haben, können wir mit der Integration ins Frontend beginnen.

Folge zuerst den Logto Quick Starts, um Logto in deine Anwendung zu integrieren.

In unserem Beispiel verwenden wir React zur Demonstration.

Nachdem du Logto in deiner Anwendung eingerichtet hast, müssen wir die RBAC-Konfigurationen hinzufügen, damit Logto funktioniert.

// frontend/src/App.tsx

const logtoConfig: LogtoConfig = {
appId: LOGTO_APP_ID, // Die App-ID, die du in der Logto Console erstellt hast
endpoint: LOGTO_ENDPOINT, // Der Endpoint, den du in der Logto Console erstellt hast
resources: [API_RESOURCE], // Der API-Ressourcen-Identifier aus der Logto Console, z. B. https://api.cms.com
// Alle Scopes, die du im Frontend von der API-Ressource anfordern möchtest
scopes: [
'list:articles',
'create:articles',
'read:articles',
'update:articles',
'delete:articles',
'publish:articles',
],
};

Denke daran, dich ab- und wieder anzumelden, damit diese Änderung wirksam wird, falls du bereits angemeldet bist.

Wenn sich der Benutzer mit Logto anmeldet und ein Zugangstoken für die oben angegebenen API-Ressourcen anfordert, fügt Logto dem Zugangstoken Berechtigungen (Scopes) hinzu, die zur Rolle des Benutzers passen.

Du kannst getAccessTokenClaims aus dem useLogto-Hook verwenden, um die Scopes aus dem Zugangstoken zu erhalten.

// frontend/src/hooks/use-user-data.ts

import { useLogto } from '@logto/react';
import { API_RESOURCE } from '../config';
import { useState, useEffect } from 'react';

export const useUserData = () => {
const { getAccessTokenClaims } = useLogto();
const [userScopes, setUserScopes] = useState<string[]>([]);
const [userId, setUserId] = useState<string>();

useEffect(() => {
const fetchScopes = async () => {
const token = await getAccessTokenClaims(API_RESOURCE);
setUserScopes(token?.scope?.split(' ') ?? []);
setUserId(token?.sub);
};

fetchScopes();
}, [getAccessTokenClaims]);

return { userId, userScopes };
};

Und du kannst userScopes verwenden, um zu prüfen, ob der Benutzer die Berechtigung zum Zugriff auf die Ressource hat.

// frontend/src/pages/Dashboard.tsx

const Dashboard = () => {
const { userId, userScopes } = useUserData();
// ...

return (
<div>
{/* ... */}
{(userScopes.includes('delete:articles') || article.ownerId === userId) && (
<button
onClick={() => handleDelete(article.id)}
className="text-red-600 hover:text-red-900"
>
Delete
</button>
)}
</div>
);
};

Dein Backend mit Logto RBAC integrieren

Jetzt ist es an der Zeit, Logto RBAC in dein Backend zu integrieren.

Backend-Autorisierungsmiddleware

Zuerst müssen wir eine Middleware im Backend hinzufügen, um Benutzerberechtigungen zu prüfen, zu verifizieren, ob der Benutzer angemeldet ist, und festzustellen, ob er die erforderlichen Berechtigungen für bestimmte APIs hat.

// backend/src/middleware/auth.js

const { createRemoteJWKSet, jwtVerify } = require('jose');

const getTokenFromHeader = (headers) => {
const { authorization } = headers;
const bearerTokenIdentifier = 'Bearer';

if (!authorization) {
throw new Error('Authorization header missing');
}

if (!authorization.startsWith(bearerTokenIdentifier)) {
throw new Error('Authorization token type not supported');
}

return authorization.slice(bearerTokenIdentifier.length + 1);
};

const hasScopes = (tokenScopes, requiredScopes) => {
if (!requiredScopes || requiredScopes.length === 0) {
return true;
}
const scopeSet = new Set(tokenScopes);
return requiredScopes.every((scope) => scopeSet.has(scope));
};

const verifyJwt = async (token) => {
const JWKS = createRemoteJWKSet(new URL(process.env.LOGTO_JWKS_URL));

const { payload } = await jwtVerify(token, JWKS, {
issuer: process.env.LOGTO_ISSUER,
audience: process.env.LOGTO_API_RESOURCE,
});

return payload;
};

const requireAuth = (requiredScopes = []) => {
return async (req, res, next) => {
try {
// Token extrahieren
const token = getTokenFromHeader(req.headers);

// Token verifizieren
const payload = await verifyJwt(token);

// Benutzerinfo zur Anfrage hinzufügen
req.user = {
id: payload.sub,
scopes: payload.scope?.split(' ') || [],
};

// Erforderliche Scopes prüfen
if (!hasScopes(req.user.scopes, requiredScopes)) {
throw new Error('Insufficient permissions');
}

next();
} catch (error) {
res.status(401).json({ error: 'Unauthorized' });
}
};
};

module.exports = {
requireAuth,
hasScopes,
};

Wie du siehst, prüfen wir in dieser Middleware, ob die Frontend-Anfrage ein gültiges Zugangstoken enthält und ob die Zielgruppe (audience) des Zugangstokens mit der in der Logto Console erstellten API-Ressource übereinstimmt.

Der Grund für die Überprüfung der API-Ressource ist, dass unsere API-Ressource tatsächlich die Ressourcen unseres CMS-Backends repräsentiert und alle unsere CMS-Berechtigungen mit dieser API-Ressource verknüpft sind.

Da diese API-Ressource die CMS-Ressourcen in Logto repräsentiert, fügen wir im Frontend-Code das entsprechende Zugangstoken bei API-Anfragen an das Backend hinzu:

// frontend/src/hooks/use-api.ts
export const useApi = () => {
const { getAccessToken } = useLogto();

return useMemo(
() =>
async (endpoint: string, options: RequestInit = {}) => {
try {
// Zugangstoken für die API-Ressource holen
const token = await getAccessToken(API_RESOURCE);

if (!token) {
throw new ApiRequestError('Failed to get access token');
}

const response = await fetch(`${API_BASE_URL}${endpoint}`, {
...options,
headers: {
'Content-Type': 'application/json',
// Zugangstoken zu den Request-Headern hinzufügen
Authorization: `Bearer ${token}`,
...options.headers,
},
});

// ... handle response

return await response.json();
} catch (error) {
// ... error handling
}
},
[getAccessToken]
);
};

Jetzt können wir die requireAuth-Middleware verwenden, um unsere API-Endpunkte zu schützen.

API-Endpunkte schützen

Für APIs, die nur für Benutzer mit bestimmten Berechtigungen zugänglich sein sollen, können wir die Einschränkungen direkt in der Middleware hinzufügen. Zum Beispiel sollte die Artikel-Erstellungs-API nur für Benutzer mit der Berechtigung create:articles zugänglich sein:

// backend/src/routes/articles.js

const { requireAuth } = require('../middleware/auth');

router.post('/articles', requireAuth(['create:articles']), async (req, res) => {
// ...
});

Für APIs, die sowohl Berechtigungen als auch Ressourcen-Eigentum prüfen müssen, können wir die Funktion hasScopes verwenden. Zum Beispiel können Benutzer mit dem Scope list:articles alle Artikel abrufen, während Autoren nur ihre eigenen erstellten Artikel sehen:

// backend/src/routes/articles.js

const { requireAuth, hasScopes } = require('../middleware/auth');

router.get('/articles', requireAuth(), async (req, res) => {
try {
// Wenn der Benutzer den Scope list:articles hat, alle Artikel zurückgeben
if (hasScopes(req.user.scopes, ['list:articles'])) {
const articles = await articleDB.list();
return res.json(articles);
}

// Andernfalls nur die Artikel des Benutzers zurückgeben
const articles = await articleDB.listByOwner(req.user.id);
res.json(articles);
} catch (error) {
res.status(500).json({ error: 'Failed to fetch articles' });
}
});

An diesem Punkt haben wir die RBAC-Implementierung abgeschlossen. Du kannst dir den vollständigen Quellcode ansehen, um die komplette Umsetzung zu sehen.

Die CMS RBAC-Implementierung testen

Jetzt testen wir unsere CMS RBAC-Implementierung mit den drei gerade erstellten Benutzern.

hinweis:

Falls du dich mit den Zugangsdaten der in „User Management“ erstellten Benutzer nicht anmelden kannst, musst du zuerst die passende Anmeldemethode aktivieren. Gehe in der Logto Console zu „Sign-in Experience“ und aktiviere deine bevorzugte Authentifizierungsmethode (wie E-Mail + Passwort oder Benutzername + Passwort).

Melden wir uns zuerst als Alex und Charles an und erstellen einige Artikel.

Da Alex die Admin-Rolle hat, kann er Artikel erstellen, löschen, aktualisieren, veröffentlichen und alle Artikel ansehen.

CMS dashboard - Alex

Charles, mit der Autor-Rolle, kann nur eigene Artikel erstellen und nur Artikel sehen, aktualisieren und löschen, die ihm gehören.

CMS dashboard - Charles - Article list

Bob, mit der Publisher-Rolle, kann alle Artikel ansehen und veröffentlichen, aber keine erstellen, aktualisieren oder löschen.

CMS dashboard - Bob

Fazit

Herzlichen Glückwunsch! Du hast gelernt, wie du ein robustes RBAC-System in deiner Anwendung implementierst.

Für komplexere Szenarien, wie den Aufbau von Multi-Tenant-Anwendungen, bietet Logto umfassende Organisationsunterstützung. Sieh dir unseren Leitfaden Multi-Tenant SaaS-Anwendung bauen: Ein vollständiger Leitfaden von Design bis Umsetzung an, um mehr über die Implementierung von organisationsweiter Zugangskontrolle zu erfahren.

Viel Spaß beim Coden! 🚀