Authentifizierung versus Autorisierung: Wo jede im Code ihren Platz hat
Authentifizierung beweist, wer Sie sind, Autorisierung entscheidet, was Sie tun können. Wo jede in Middleware gehört, 401 gegenüber 403, sowie die Fehler durch Verwechslung beider.
Quellen: Authentication vs Authorization: The Distinction Most Developers Get Wrong
Zwei Begriffe, die beim Überfliegen fast austauschbar erscheinen, doch jeder regelt einen völlig unterschiedlichen Teil der Logik Ihres Systems.
Falls Sie jemals in einer Situation, in der eigentlich ein 403 erforderlich war, einen 401 zurückgesendet haben, oder einen Anmeldeprozess implementiert und die Authentifizierung für abgeschlossen erklärt haben, ohne jemals die Berechtigungslogik zu berücksichtigen, sind Sie bereits auf die Probleme gestoßen, die aus dem Verwechseln dieser beiden Konzepte entstehen. Lassen Sie uns sie endgültig auseinanderklären.
Authentifizierung: Überprüfung der Identität
Authentifizierung ist der Schritt, bei dem das System bestätigt, dass ein Benutzer tatsächlich die Person ist, für die er sich ausgibt. Sie beantwortet eine einzige Frage: Stimmt diese Identität?
Es handelt sich um ein einmaliges Ereignis, das in der Regel beim Anmelden stattfindet, und es erzeugt eine Art Zugangsdaten – eine Sitzung, einen Token oder ein Cookie –, das es dem Rest der Anwendung ermöglicht, dem Aufrufer zu vertrauen, ohne bei jeder einzelnen Anfrage das Passwort erneut zu überprüfen.
Typische Methoden zur Implementierung der Authentifizierung sind:
- Passworts basierende Authentifizierung – die eingegebenen Zugangsdaten werden mit einem gehashten Wert in der Speicherung verglichen
- Token basierende Authentifizierung (JWT) – ein signierter Token wird nach einem erfolgreichen Anmelden generiert und bei jeder anschließenden Anfrage überprüft
- OAuth 2.0 / OpenID Connect – die Identitätsüberprüfung wird an einen externen Anbieter wie Google oder GitHub übertragen
- API-Schlüssel – eine feste geheime Zeichenkette, die einen bestimmten Client oder Dienst identifiziert
So sieht ein standardmäßiger Authentifizierungsablauf aus:
POST /api/login
Content-Type: application/json
{
"email": "user@example.com",
"password": "hashed_and_verified_serverside"
}
Response: 200 OK
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
Der Server überprüft die eingereichten Anmeldeinformationen und gibt bei Erfolg ein Token zurück. Von diesem Zeitpunkt an dient dieses Token als Nachweis der Identität des Anfragenden bei jeder Anfrage:
GET /api/profile
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Das Middleware-Element, das für die Überprüfung dieses Tokens zuständig ist, sieht in der Regel so aus:
function authenticate(req, res, next) {
const token = req.headers.authorization?.split(' ')[1];
if (!token) return res.status(401).json({ error: 'No token provided' });
jwt.verify(token, process.env.JWT_SECRET, (err, decoded) => {
if (err) return res.status(401).json({ error: 'Invalid or expired token' });
req.user = decoded;
next();
});
}
Achten Sie darauf, was dieses Middleware-Modul absichtlich weglässt. Zu keinem Zeitpunkt entscheidet es darüber, was der Benutzer tun darf. Es bestätigt lediglich, dass das Token gültig ist, und fügt die decodierte Nutzlast dem Anfrageobjekt hinzu. Mehr gehört nicht zur Aufgabenbeschreibung der Authentifizierung.
Autorisierung: Überprüfung von Berechtigungen
Autorisierung tritt erst ein, nachdem die Authentifizierung bereits erfolgreich war. Sie beantwortet eine völlig separate Frage: Ist diese überprüfte Identität berechtigt, diese bestimmte Aktion auszuführen?
Zu den gängigen Ansätzen zur Strukturierung der Autorisierung gehören:
- Rollenbasierte Zugriffskontrolle (RBAC) – Berechtigungen werden Rollen wie
admin,editoroderviewerzugeordnet
read:messages oder write:files.Eine typische Autorisierungsprüfung erfolgt auf einer bereits authentifizierten Anfrage:
function authorize(requiredRole) {
return (req, res, next) => {
if (req.user.role !== requiredRole) {
return res.status(403).json({ error: 'Insufficient permissions' });
}
next();
};
}
app.get('/api/admin/dashboard', authenticate, authorize('admin'), (req, res) => {
res.json({ data: 'sensitive admin data' });
});
Dies sind zwei unterschiedliche Middleware-Funktionen, die sich mit zwei verschiedenen Aufgaben beschäftigen und in einer festgelegten Reihenfolge ausgeführt werden: authenticate bestimmt, wer der Aufrufer ist, und authorize entscheidet, ob diesem Aufrufer die Weiterverarbeitung gestattet ist. Eine Anfrage kann die erste Überprüfung bestehen und dennoch von der zweiten abgelehnt werden – wobei die Ablehnung dort nichts mit der Identität zu tun hat.
Die Statuscodes, die diesen Unterschied tatsächlich darstellen
Hier verwandelt sich der theoretische Unterschied in etwas, das man buchstäblich im Code überprüfen kann.
401 Unauthorized – der Name ist irreführend, doch dieser Code signalisiert tatsächlich ein Authentifizierungsproblem. Er bedeutet, dass der Server überhaupt keine Anmeldeinformationen erhalten hat oder die erhaltenen Informationen (einschließlich eines Tokens) ungültig oder abgelaufen sind. Zu diesem Zeitpunkt hat der Server keinerlei Klarheit darüber, wer die Anfrage stellt.
403 Forbidden – dies signalisiert ein Autorisierungsproblem. Der Server hat bereits ermittelt, wer der Anrufer ist; er weigert sich einfach, dieser Identität die angeforderte Aktion auszuführen oder auf den angeforderten Ressourcenzugriff zu gewähren.
// Authentication failure — identity unknown or invalid
res.status(401).json({ error: 'Invalid credentials' });
// Authorization failure — identity known, permission denied
res.status(403).json({ error: 'You do not have access to this resource' });
Die Rückgabe eines 401-Status für ein Problem, das eigentlich eine Berechtigungsfrage ist, ist ein häufiger Fehler. Dadurch wird der Client aufgefordert, erneut einzuloggen, obwohl das eigentliche Problem darin besteht, dass der Login einwandfrei funktioniert – dem Konto fehlt einfach die erforderliche Rolle. Allein dieser Fehler kann aus einer eigentlich schnellen Lösung ein langes, sinnloses Debugging auf der Clientseite machen, bei dem nach einem nie existierenden Login-Problem gesucht wird.
Wo dies in echten Architekturen vorkommt
JWT-Payloads vermischen häufig diese Aspekte, ohne dass jemand es beabsichtigt. Ein Token soll Identitätsdaten enthalten – Dinge wie sub, email, iat und exp. Ob es auch Berechtigungsdaten wie role oder scope mitführen sollte, ist eine gestalterische Entscheidung, die vom System abhängt. Die direkte Aufnahme von Rollen in das Token ist praktisch und vermeidet zusätzliche Abfragen, hat aber einen echten Nachteil: Jede Änderung der Berechtigungen eines Benutzers tritt erst dann in Kraft, wenn das Token erneut ausgestellt wird. Es handelt sich dabei um ein echtes Abwägungsproblem, nicht um einen Shortcut ohne Nachteile.
{
"sub": "user_12345",
"email": "user@example.com",
"role": "editor",
"iat": 1710000000,
"exp": 1710003600
}
Systeme, die auf Server-seitigen Sessions basieren, neigen dazu, diese beiden Aspekte weiter voneinander zu trennen. Der Session-Identifikator kümmert sich um die Authentifizierung, während Berechtigungen bei jeder Anfrage neu aus einer Datenbank abgerufen werden. Dadurch wird das oben beschriebene Problem der Veralteten Informationen umgangen – allerdings zum Preis einer zusätzlichen Datenbankanfrage pro Anfrage.
In Microservice-Architekturen ist die Authentifizierung in der Regel zentralisiert und wird einmalig von einem Gateway oder einem Identitätsanbieter wie Auth0, Keycloak oder AWS Cognito durchgeführt, der anschließend ein Token ausstellt. Die Autorisierung hingegen wird in der Regel jedem einzelnen Service überlassen, der die Angaben in diesem Token liest und seine eigenen Regeln durchsetzt. Das Ergebnis ist, dass ein und dasselbe Authentifizierungsereignis von zehn verschiedenen Services unterschiedlich bewertet werden kann, wobei jeder Service eigenständig entscheidet, was mit dieser Identität möglich ist.
Die Regel, die die meisten Sicherheitsfehler verhindert
Die Authentifizierung muss vor der Autorisierung erfolgen, und die beiden Schritte dürfen niemals zu einer einzigen kombinierten Überprüfung zusammengefasst werden.
// Correct: sequential, separate concerns
app.post('/api/posts/:id/delete', authenticate, authorize('editor'), deleteHandler);
// Wrong: conflating identity with permission in one step
app.post('/api/posts/:id/delete', (req, res) => {
if (req.headers.authorization === 'valid-token-and-also-admin') {
// fragile, unscalable, and impossible to audit correctly
}
});
Ein System, das strenge Authentifizierung durchführt, aber die Autorisierung überspringt, bestätigt die Identität fehlerfrei und erlaubt anschließend jeder bestätigten Identität, zu tun, was sie will – somit bedeutet ein kompromittierter Account mit geringen Berechtigungen im Grunde einen vollständigen Sicherheitsbruch. Umgekehrt hat ein System mit fein abgestimmter Autorisierung, das auf schwacher Authentifizierung beruht, das gegenteilige Problem: Sorgfältig definierte Berechtigungen schützen eine Tür, durch die jeder ungehindert eintreten kann.
Sowohl Schichten müssen solide sein, in der richtigen Reihenfolge angewendet werden und als getrennte Aspekte betrachtet werden. Das ist der ganze Unterschied – und sobald dies richtig in Ihren Statuscodes, Ihrer Middleware-Kette sowie in der Gestaltung Ihrer Tokens berücksichtigt wird, verschwindet eine ganze Klasse von Sicherheitsfehlern einfach.