Client-seitige vs. Server-seitige Autorisierung: Warum Sie beide benötigen
Client- vs. Server-Autorisierung in React und Next.js: Rechte serverseitig erzwingen, Client für UX nutzen und 403-Fehlabstimmungen vermeiden.
Client-seitige Autorisierung ist die Zugriffslogik, die im Browser ausgeführt wird und beeinflusst, was Benutzer sehen; server-seitige Autorisierung ist die Zugriffslogik, die auf dem Server ausgeführt wird und entscheidet, was tatsächlich passiert – und kein noch so ausgefeiltes UI-Gating ändert, was der Server akzeptiert. Dies sind zwei verschiedene Aufgaben an zwei verschiedenen Vertrauensgrenzen, und das Auslassen einer der beiden führt zu einem spezifischen, vorhersehbaren Fehler: Rein client-seitige Prüfungen können von jedem umgangen werden, der DevTools öffnet, und rein server-seitige Prüfungen lassen Benutzer auf Schaltflächen klicken, die 403-Fehler zurückgeben, ohne dass sie verstehen, warum.
Wenn Sie eine React- oder Next.js-Anwendung entwickelt haben, in der Rollenprüfungen in if (user.role === 'admin')-Blöcken stehen, haben Sie bereits client-seitige Autorisierung implementiert – Sie sind sich möglicherweise nur nicht sicher, wo die eigentliche Sicherheitsgrenze liegt. Dieser Artikel zieht diese Grenze präzise: warum der Client nicht vertrauenswürdig ist, warum der Server der einzige Kontrollpunkt ist, der wirklich zählt, wofür client-seitige Prüfungen legitim sind, und wie beide Schichten mithilfe eines einzigen Berechtigungsbezeichners, der auf beiden Seiten durchgesetzt wird, synchron gehalten werden können.
Wichtige Erkenntnisse
- Der Client ist eine nicht vertrauenswürdige Umgebung: Jeder Benutzer kann DevTools öffnen, die Variable mit seiner Rolle ändern und beobachten, wie Ihre bedingten Renderings darauf reagieren – daher müssen Autorisierungsentscheidungen server-seitig, an einem Gateway oder in einer Serverless-Funktion durchgesetzt werden (OWASP Authorization Cheat Sheet).
- Client-seitige Autorisierung dient der Benutzerfreundlichkeit, nicht der Sicherheit: Sie verbirgt Schaltflächen, sperrt Routen und zeigt nur die Menüpunkte an, die relevant sind, damit Benutzer nie in einer Sackgasse landen.
- Der Berechtigungsstatus des Frontends ist ein Cache dessen, was der Server erlaubt; wenn beide auseinanderdriften, entsteht entweder ein manipulierbarer Workflow (die UI zeigt eine Schaltfläche, die der Server mit 403 ablehnt) oder eine versteckte Funktion (der Server erlaubt eine Aktion, die die UI nie anzeigt).
- Verwenden Sie Berechtigungsbezeichner wie
tasks:deleteauf beiden Schichten, keine Rollenvergleiche wierole === 'admin'– Berechtigungsnamen überstehen Rollenumstrukturierungen und entsprechen der Einheit, die der Server tatsächlich durchsetzt. - Ein 401 bedeutet nicht authentifiziert und sollte zur Anmeldeseite weiterleiten; ein 403 bedeutet authentifiziert, aber nicht autorisiert und sollte einen Zugriff-verweigert-Zustand anzeigen (RFC 9110 §15.5.2, §15.5.4).
Der Client ist eine nicht vertrauenswürdige Umgebung
Jede Rollenprüfung in Ihrem JavaScript läuft in einer Umgebung, die der Benutzer kontrolliert: Er kann DevTools öffnen, die Variable mit seiner Rolle finden, sie auf 'admin' setzen und beobachten, wie Ihre bedingten Renderings reagieren – einzig die Antwort des Servers auf die nächste Anfrage ändert sich nicht. Dies ist die grundlegende Tatsache, die client-seitige Autorisierung allein unzureichend macht. Der Browser führt Ihren Code aus, aber der Benutzer besitzt den Browser und kann alles zwischen dem Laden der Seite und der nächsten Netzwerkanfrage umschreiben.
Das OWASP Authorization Cheat Sheet formuliert die praktische Regel direkt: Client-seitige Zugriffskontrollprüfungen können die Benutzerfreundlichkeit verbessern, aber Autorisierungsentscheidungen müssen server-seitig, an einem Gateway oder in einer Serverless-Funktion durchgesetzt werden – da client-seitige Logik leicht zu umgehen ist. Der konzeptionelle Rahmen geht weit vor modernen SPAs zurück; der Static Apps-Leitfaden zur Authentifizierung formulierte es so: „Der Endbenutzer kann beliebigen Code auf der Client-Seite ohne vorherige Genehmigung ausführen.”
Eine 30-Sekunden-Umgehung mit DevTools
Die anschaulichste Demonstration, warum rein client-seitige Autorisierung scheitert, ist es, sie selbst zu umgehen. Dies ist in jedem Browser mit DevTools reproduzierbar, gegen jede React-Anwendung, die die Rolle im Komponentenstatus speichert, anstatt sie bei jeder Anfrage aus einem server-verifizierten Token abzuleiten:
- Melden Sie sich als normaler (Nicht-Admin-)Benutzer an. Das Admin-Panel ist ausgeblendet – Ihre Komponente rendert
{user.role === 'admin' && <AdminPanel />}unduser.roleist'user'. - Öffnen Sie DevTools und suchen Sie den Komponentenstatus, der
userenthält. Mit React DevTools können Sie Hook-Status direkt inspizieren und bearbeiten; ohne diese funktioniert jeder Codepfad, der das Benutzerobjekt einer veränderbaren Referenz zugänglich macht. - Setzen Sie
roleauf'admin'. React rendert neu. Das Admin-Panel erscheint im DOM. - Klicken Sie auf die Schaltfläche zum Löschen, die das Panel anzeigt. Die Anfrage wird gesendet.
- Wenn der Server die Autorisierung durchsetzt, liest er die Identität aus Ihrem Token – nicht aus Ihrem veränderten Client-Status – und gibt
403 Forbiddenzurück. An den Daten hat sich nichts geändert.
Der Angriff gelingt bei Schritt 3 (die UI ändert sich) und scheitert bei Schritt 5 (der Server lehnt die Anfrage ab) – aber nur wenn eine Server-Prüfung vorhanden ist. Ohne Server-Prüfung verändert Schritt 4 echte Daten. Die Umgehung funktioniert speziell deshalb, weil die Rolle in einem veränderbaren Client-Status gespeichert ist; sie funktioniert nicht, wenn der Server die Autorisierung bei jeder Anfrage aus einem verifizierten Token neu ableitet.
Der Server ist der einzige Kontrollpunkt, der wirklich zählt
Discover how at OpenReplay.com.
Autorisierungsentscheidungen, die Daten verändern oder geschützte Ressourcen zugänglich machen, müssen auf dem Server getroffen und durchgesetzt werden, da der Server der einzige Teilnehmer in der Anfrage ist, den der Benutzer nicht umschreiben kann. Das Backend ist der letzte Kontrollpunkt: Unabhängig davon, was das Frontend rendert, verbirgt oder deaktiviert, ist die Auswertung der Anfrage durch den Server die maßgebliche Entscheidung. Frontend-Kontrollen sind kein Ersatz für Backend-Durchsetzung.
Hier ist die server-seitige Durchsetzung einer einzelnen Berechtigung, tasks:delete, als Next.js Route Handler. Die Berechtigung wird aus der authentifizierten Sitzung gelesen, nicht aus dem, was der Client im Body sendet:
// app/api/tasks/[id]/route.ts — Next.js App Router
import { NextRequest, NextResponse } from 'next/server';
import { getSessionPermissions } from '@/lib/auth';
import { deleteTask } from '@/lib/tasks';
export async function DELETE(
req: NextRequest,
{ params }: { params: { id: string } },
) {
const permissions = await getSessionPermissions(req); // abgeleitet aus einem verifizierten Token/einer Sitzung
if (!permissions.includes('tasks:delete')) {
return NextResponse.json(
{ error: 'forbidden', permission: 'tasks:delete' },
{ status: 403 },
);
}
await deleteTask(params.id);
return new NextResponse(null, { status: 204 });
}
Der 403-Fehler enthält den spezifischen Berechtigungsnamen im Body. Dieses Detail ist wichtig: Es ermöglicht dem Client, eine Berechtigungsverweigerung von anderen Fehlern zu unterscheiden und eine aussagekräftige Meldung anzuzeigen, anstatt einer generischen Benachrichtigung. Der Status 403 Forbidden ist die korrekte semantische Wahl gemäß RFC 9110 §15.5.4, der ihn als den Fall definiert, in dem der Server die Anfrage versteht, aber die Autorisierung verweigert.
Client-seitige Autorisierung dient der Benutzeroberfläche, nicht der Absicherung
Client-seitige Autorisierung dient dazu, die Benutzeroberfläche zu gestalten: Admin-Schaltflächen vor Nicht-Admins zu verbergen, Routen zu sperren, damit Benutzer nicht auf einem 403-Fehler landen, und nur die relevanten Menüpunkte anzuzeigen. Sie verbessert die Benutzererfahrung, indem sie Benutzer zu Aktionen führt, die sie tatsächlich ausführen können – sie sichert jedoch nichts und kann nichts sichern. Stellen Sie sich das Frontend als Schaufenster vor, nicht als Tresor: Es ordnet an, was erreichbar und verfügbar ist, während das Schloss auf dem Server bleibt.
Die Standardimplementierung liest Berechtigungen aus dem Kontext und rendert bedingt anhand eines Berechtigungsbezeichners:
// components/TaskActions.tsx
'use client';
import { usePermissions } from '@/hooks/usePermissions';
export function TaskActions({ taskId }: { taskId: string }) {
const { can } = usePermissions();
return (
<div className="task-actions">
{can('tasks:delete') && (
<button onClick={() => deleteTask(taskId)}>Delete</button>
)}
</div>
);
}
Beachten Sie den Bezeichner: tasks:delete, nicht role === 'admin'. Die Prüfung user.role === 'admin' koppelt Ihre UI an Ihre Rollentaxonomie; die Prüfung can('tasks:delete') koppelt sie an die Aktion, die der Server tatsächlich durchsetzt – und dem Server ist es egal, welche Rolle sie gewährt hat. Wenn Sie später admin in admin und billing-admin aufteilen, bleiben berechtigungsbasierte Prüfungen unverändert funktionsfähig. Das OWASP Authorization Cheat Sheet empfiehlt die Trennung von Rollen und Berechtigungen genau aus diesem Grund. RBAC bleibt ein verbreitetes Zugriffskontrollmodell für Frontend-Anwendungen; für die vollständige Frage der Auswahl zwischen RBAC/ABAC/ACL/PBAC bietet LogRockets Vergleich von Zugriffskontrollmodellen eine gute Übersicht.
Ein handwerklicher Hinweis: Session-Replays von rollengesteuerten UIs zeigen häufig den kurzen Moment, in dem ein nicht autorisierter Benutzer die Admin-Schaltfläche sieht, bevor eine client-seitig abgerufene Berechtigungsprüfung aufgelöst wird und sie verbirgt – ein kurzes Aufblitzen einer nicht autorisierten UI, verursacht dadurch, dass der Berechtigungsstatus nach dem ersten Rendering eintrifft. Die Lösung besteht darin, den Berechtigungsstatus server-seitig zu rendern oder ihn vor dem Rendering vorab zu laden, was das Übergabemuster im nächsten Abschnitt durch sein Design gewährleistet.
Die beiden Schichten müssen übereinstimmen: Berechtigungsdrift
Die Sicht des Frontends auf Berechtigungen ist ein Cache dessen, was der Server erlaubt, und beide müssen synchron bleiben. Wenn der Berechtigungsstatus des Frontends von den Durchsetzungsregeln des Servers abweicht, entstehen zwei mögliche Fehler: eine Schaltfläche, die der Server mit 403 ablehnt (ein manipulierbarer Workflow), oder eine Aktion, die der Server erlaubt, die die UI aber nie anzeigt (eine versteckte Funktion). Der erste Fall ist ein Sicherheits- und UX-Problem – Sie zeigen Bedienelemente an, die nicht funktionieren, und ein Angreifer kann herausfinden, welche der Server tatsächlich akzeptiert. Der zweite Fall ist ein reiner Funktionsverlust – Benutzer können etwas nicht erreichen, wozu sie berechtigt sind.
Der Weg, Drift zu verhindern, ist strukturell: Definieren Sie eine einzige Quelle der Wahrheit und speisen Sie beide Schichten daraus.
Das Übergabemuster
Das Übergabemuster ist eine Autorisierungsarchitektur, bei der der Server die einzige Quelle der Wahrheit für Berechtigungen hält und der Client einen Cache dieser Wahrheit hält – befüllt bei der Anmeldung, verwendet zum Rendern der Benutzeroberfläche, aber beratend statt maßgeblich. Der vollständige Kreislauf läuft folgendermaßen: Der Server bestimmt die Berechtigungen des Benutzers, sendet sie in einem bekannten Übertragungsformat an den Client, der Client speichert sie im Kontext, die UI rendert anhand des Caches, der Benutzer handelt, und der Server verifiziert bei der Anfrage erneut. Derselbe Berechtigungsbezeichner erscheint bei jedem Schritt.
Beginnen Sie mit dem Übertragungsformat. Bei der Anmeldung (oder beim initialen Server-Component-Rendering) gibt der Server den Berechtigungssatz aus:
{
"userId": "u_8123",
"permissions": ["tasks:read", "tasks:create", "tasks:delete"]
}
Wenn Sie Berechtigungen in einem JWT übertragen, befinden sie sich als Claims in der Token-Nutzlast. Ein signiertes JWT (ein JWS) enthält Claims in einer base64url-kodierten Nutzlast, die integritätsgeschützt, aber nicht verschlüsselt ist – die Signatur beweist, dass die Claims nicht manipuliert wurden, aber die Claims sind nicht geheim und für den Client lesbar. Verschlüsselte JWTs (JWEs) sind eine andere Konstruktion. Diese Unterscheidung stammt aus RFC 7519, der JWT-Spezifikation. Die praktische Konsequenz: Die Berechtigungs-Claims eines JWTs sind ein hervorragender Cache für das Rendering, aber sie sind immer noch nur ein Cache. Der Server sollte die Autorisierung bei jeder Anfrage anhand der Quelle der Wahrheit validieren, auf die Ihre Architektur sich stützt.
Laden Sie den Cache einmalig in den Kontext, idealerweise aus einer Server-Komponente, damit die Daten beim ersten Rendering vorhanden sind:
// app/layout.tsx — Server Component, Next.js App Router
import { getSessionPermissions } from '@/lib/auth';
import { PermissionsProvider } from '@/hooks/usePermissions';
export default async function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
const permissions = await getSessionPermissions(); // dieselbe Quelle, die die API verwendet
return (
<html lang="en">
<body>
<PermissionsProvider permissions={permissions}>
{children}
</PermissionsProvider>
</body>
</html>
);
}
// hooks/usePermissions.tsx
'use client';
import { createContext, useContext } from 'react';
const PermissionsContext = createContext<string[]>([]);
export function PermissionsProvider({
permissions,
children,
}: {
permissions: string[];
children: React.ReactNode;
}) {
return (
<PermissionsContext.Provider value={permissions}>
{children}
</PermissionsContext.Provider>
);
}
export function usePermissions() {
const permissions = useContext(PermissionsContext);
return { can: (p: string) => permissions.includes(p) };
}
Da getSessionPermissions() dieselbe Funktion ist, die der Route Handler aufruft, lesen UI und Kontrollpunkt identische Daten, und tasks:delete bedeutet auf beiden Seiten dasselbe. Das Laden von Berechtigungen in einer Server-Komponente (anstatt sie in einem client-seitigen useEffect abzurufen) bedeutet, dass der Berechtigungssatz vor dem ersten Rendering verfügbar ist – kein Aufblitzen der falschen UI, kein Flackern während eine Prüfung aufgelöst wird.
React Server Components und Server Actions: Die Grenze ist syntaktisch
Im Next.js App Router beschreiben die Begriffe „Client” und „Server”, wo Code ausgeführt wird, nicht einen Netzwerkaufruf, den Sie manuell durchführen – und genau diese Unterscheidung macht Server Actions zu einem legitimen Ort für die Durchsetzung von Autorisierung. Eine Next.js Server Action, die eine Berechtigungsprüfung durchführt, bevor sie Daten verändert, ist server-seitige Autorisierung, unabhängig davon, wo sie im Komponentenbaum sitzt: Der Code läuft auf dem Server, der Benutzer kann ihn nicht modifizieren, und die Prüfung kann nicht durch Bearbeitung des Client-Status umgangen werden.
In der aktuellen Next.js App Router-Dokumentation markiert die 'use server'-Direktive eine Server Function; eine Server Function, die in einem Aktions-/Mutationskontext verwendet wird, wird auch als Server Action bezeichnet. Die Direktive ist die Grenzmarkierung – Code darunter wird auf dem Server ausgeführt, auch wenn er in einer .tsx-Datei neben Client-Komponenten geschrieben ist:
// app/tasks/actions.ts — Next.js App Router
'use server';
import { getSessionPermissions } from '@/lib/auth';
import { deleteTask } from '@/lib/tasks';
export async function deleteTaskAction(taskId: string) {
const permissions = await getSessionPermissions(); // läuft auf dem Server
if (!permissions.includes('tasks:delete')) {
return { ok: false as const, error: 'forbidden', permission: 'tasks:delete' };
}
await deleteTask(taskId);
return { ok: true as const };
}
Dies ist dieselbe tasks:delete-Prüfung wie im Route Handler, an derselben Durchsetzungsposition. Das Bearbeiten des Client-Status, um role === 'admin' vorzutäuschen, hat hier keine Wirkung – die Action leitet Berechtigungen auf dem Server aus der Sitzung neu ab. Server Actions beseitigen nicht die Notwendigkeit des Kontrollpunkts; sie sind ein weiterer Ort, an dem der Kontrollpunkt sitzt. Weitere Informationen zur vollständigen Semantik der Direktive finden Sie in der Next.js use server-Direktiven-Dokumentation.
Wenn Ihre SPA eine Drittanbieter-API direkt aufruft
Wenn Ihre SPA eine Drittanbieter-API direkt vom Browser aus aufruft, ist der Drittanbieter für Autorisierungszwecke Ihr Server – ein Zahlungsanbieter, ein Headless CMS oder ein Backend-as-a-Service ist der Kontrollpunkt, unabhängig davon, ob Sie ihn geschrieben haben. Die Durchsetzungsgrenze verschwindet nicht, weil Sie das Backend nicht geschrieben haben; sie verlagert sich zu demjenigen, der die Anfrage auswertet. Ihre client-seitigen Prüfungen sind weiterhin nur für die UX, und die Autorisierungsregeln des Drittanbieters sind der Kontrollpunkt. Wenn Sie eine Entscheidung nicht in Code durchsetzen können, den Sie kontrollieren, leiten Sie die Anfrage über Ihr eigenes Backend weiter.
Fehlermodi: Was schiefgeht, wenn Sie eine Schicht weglassen
Das Weglassen einer der beiden Schichten führt zu einem spezifischen, vorhersehbaren Fehler. Rein client-seitige Autorisierung ist trivial zu umgehen; rein server-seitige Autorisierung lässt Benutzer verwirrt zurück. Keine der beiden ist allein akzeptabel.
| Weggelassene Schicht | Was schiefgeht | Wer nutzt es aus | Benutzererfahrung |
|---|---|---|---|
| Server-seitig (nur Client) | Autorisierung wird überhaupt nicht durchgesetzt | Jeder Benutzer mit DevTools oder curl | Sieht gut aus – bis Daten von jemandem geändert werden, der das nicht sollte |
| Client-seitig (nur Server) | UI zeigt Aktionen an, die der Server ablehnt | Kein Angriff; es ist ein UX-Defekt | Benutzer klicken auf eine Schaltfläche, erhalten einen 403-Fehler, klicken erneut, geben auf |
Die Regel ist kurz genug, um sie sich zu merken: Mit Client-Berechtigungen rendern, mit Server-Berechtigungen durchsetzen, und dieselben Berechtigungsbezeichner an beide von einer einzigen Quelle der Wahrheit auf dem Server liefern. Verletzen Sie den ersten Teil, klicken Benutzer auf Schaltflächen, die 403-Fehler zurückgeben. Verletzen Sie den zweiten Teil, kann sich jeder Benutzer mit DevTools selbst höhere Rechte verschaffen.
Die UX von Autorisierungsfehlern: 403 vs. 401
Wenn der Server eine Aktion ablehnt, die die UI für erlaubt hielt, teilt Ihnen der Antwort-Statuscode genau mit, was dem Benutzer angezeigt werden soll. Ein 401 bedeutet, dass der Benutzer nicht authentifiziert ist – weiterleiten zur Anmeldung. Ein 403 bedeutet, dass er authentifiziert, aber nicht autorisiert ist – zeigen Sie einen Zugriff-verweigert-Zustand an, keinen generischen Fehler, damit der Benutzer versteht, warum die Aktion fehlgeschlagen ist. Diese Semantik ist durch RFC 9110 festgelegt: §15.5.2 definiert 401 Unauthorized (Authentifizierung ist erforderlich und ist fehlgeschlagen oder wurde nicht bereitgestellt), und §15.5.4 definiert 403 Forbidden (der Server hat die Anfrage verstanden, verweigert aber die Autorisierung).
async function deleteTask(taskId: string) {
const res = await fetch(`/api/tasks/${taskId}`, { method: 'DELETE' });
if (res.status === 401) {
window.location.assign('/login');
return;
}
if (res.status === 403) {
const body = await res.json();
showPermissionDenied(body.permission); // z.B. "Sie haben keine Berechtigung, Aufgaben zu löschen."
return;
}
if (!res.ok) {
showError('Etwas ist schiefgelaufen. Bitte versuchen Sie es erneut.');
return;
}
// Erfolg
}
Die gesonderte Behandlung von 403-Fehlern ist der Punkt, an dem die Client/Server-Drift für den Benutzer sichtbar wird. Session-Replays von 403-Antworten zeigen häufig, wie ein Benutzer auf eine Schaltfläche klickt, eine generische Fehlermeldung erhält und dann erneut klickt – ein Zeichen dafür, dass die UI nie kommuniziert hat, dass die Aktion speziell aus Berechtigungsgründen abgelehnt wurde. Das Anzeigen des Berechtigungsnamens aus dem Response-Body (das Feld permission: 'tasks:delete', das der Server gesendet hat) schließt diese Lücke. Der RFC definiert, was 403 bedeutet; wie Sie es präsentieren, liegt bei Ihnen, aber die Bedeutung ist das, was die UI widerspiegeln sollte.
Fazit
Autorisierung existiert an zwei Grenzen mit zwei Aufgaben: Der Client gestaltet die Benutzeroberfläche, der Server entscheidet, was passiert, und dieselben Berechtigungsbezeichner fließen von einer einzigen Quelle der Wahrheit auf dem Server an beide. Überprüfen Sie Ihren eigenen Code auf die zwei Fehlermodi – suchen Sie nach Rollen- oder Berechtigungsprüfungen, die Datenmutationen absichern, ohne eine entsprechende server-seitige Durchsetzung zu haben (eine Umgehung, die nur darauf wartet, ausgenutzt zu werden), und suchen Sie nach client-seitigen Prüfungen, die von dem abgewichen sind, was Ihre API zurückgibt (ein 403-Fehler, den Ihre Benutzer nicht nachvollziehen können). Überall dort, wo eine Prüfung Daten verändert, stellen Sie sicher, dass der Server die Entscheidung aus einer verifizierten Sitzung neu ableitet, nicht aus etwas, das der Client umschreiben kann.
Häufig gestellte Fragen
Ist es sicher, Benutzerberechtigungen in einem JWT zu speichern, wenn der Client sie lesen kann?
Ja, solange Sie das Token als Cache und nicht als Sicherheitsgrenze behandeln. Ein signiertes JWT (ein JWS) enthält Claims in einer base64url-kodierten Nutzlast, die integritätsgeschützt, aber nicht verschlüsselt ist, sodass der Client die Berechtigungen lesen kann, die Signatur jedoch Manipulationen verhindert. Das Risiko liegt nicht in der Offenlegung der Berechtigungsliste; es liegt darin, ihr zu vertrauen. Der Server sollte die Autorisierung bei jeder Anfrage gemäß dem Autorisierungsmodell validieren, das von der Anwendung verwendet wird.
Benötigt eine Next.js Server Action eine eigene Autorisierungsprüfung, wenn die Seite die UI bereits gesperrt hat?
Ja. UI-Sperren kontrollieren nur, was gerendert wird; sie schützen nicht die Mutation. Eine Server Action läuft auf dem Server und kann unabhängig von der Seite aufgerufen werden, die sie gerendert hat. Daher muss sie Berechtigungen aus der verifizierten Sitzung neu ableiten und nicht autorisierte Aufrufe selbst ablehnen. Die 'use server'-Direktive markiert, wo Code ausgeführt wird, nicht ob er autorisiert ist. Behandeln Sie jede Server Action als Durchsetzungspunkt, genau wie einen Route Handler.
Welchen Statuscode sollte eine API zurückgeben, wenn ein Benutzer angemeldet ist, aber keine Berechtigung für eine Aktion hat?
Geben Sie 403 Forbidden zurück, nicht 401 Unauthorized. RFC 9110 definiert 401 als den Fall, in dem Authentifizierung erforderlich ist und fehlgeschlagen ist oder nicht bereitgestellt wurde, und 403 als den Fall, in dem der Server die Anfrage versteht, aber die Autorisierung verweigert. Ein angemeldeter Benutzer ohne Berechtigung ist authentifiziert, aber nicht autorisiert – das ist genau 403. Der Client sollte bei 401 zur Anmeldung weiterleiten und bei 403 einen Zugriff-verweigert-Zustand anzeigen, damit die beiden Fehler visuell und verhaltenstechnisch unterscheidbar bleiben.
Warum 'tasks:delete' statt 'role === admin' im Frontend prüfen?
Berechtigungsbezeichner überstehen Rollenumstrukturierungen; Rollennamensvergleiche nicht. Die Prüfung 'role === admin' koppelt die UI an Ihre aktuelle Rollentaxonomie, sodass das spätere Aufteilen von admin in admin und billing-admin jede Prüfung bricht. Die Prüfung 'tasks:delete' koppelt die UI an die Aktion, die der Server tatsächlich durchsetzt, was sich nicht ändert, wenn Rollen umorganisiert werden.