Nicht in jedem Projekt hat man von Beginn an eine umfassende Identity-Management-Lösung wie Keycloak im Einsatz – sei es aus Zeit- oder Budgetgründen, wegen des Projektumfangs oder aufgrund von Ressourcenrestriktionen. Wie kann man trotzdem sicherstellen, dass sensible API-Endpunkte nicht ungeschützt bleiben, wenn API-Tokens in einem Angular Frontend nicht sicher abgelegt werden können?
Hier bietet sich der Einsatz von JSON Web Tokens (JWTs) als eine pragmatische und dennoch sichere Lösung an. JWTs ermöglichen eine leichte, schnelle und flexible Absicherung von APIs, ohne dass gleich ein vollwertiges IAM-System aufgebaut werden muss.
In diesem Beitrag zeige ich auszugsweise eine Implementierung aus einem realen Projekt, in der wir mit minimalem Setup und ein paar Zeilen Code die Kommunikation zwischen einer Angular-Applikation und einer Node.js/Express-Middleware sicherer gestalten konnten.
JWT Konfiguration und Erstellung in Node.js / Express
Bevor wir den JSON Web Token (JWT) ausstellen und an unser Angular-Frontend übermitteln, benötigen wir die passenden Abhängigkeiten und ein sicheres Setup.
1. Abhängigkeiten und Konfiguration
In unserem Express-Backend verwenden wir das Paket jsonwebtoken, das sowohl die Erstellung als auch die Verifizierung von JWTs ermöglicht. Für die einmalige Generierung eines sicheren Signing-Secrets greifen wir zusätzlich auf das Node.js-eigene Paket crypto zurück. Dieses kann nach der Erzeugung wieder entfernt oder durch einen anderen Weg zur Schlüsselerzeugung ersetzt werden – vorausgesetzt, dieser erfüllt die empfohlenen Sicherheitsanforderungen.
const jwt = require('jsonwebtoken');
const crypto = require('crypto');
const secretKey = crypto.randomBytes(32).toString('hex');
Das erzeugte Secret wird anschließend statisch in einer .env-Datei gespeichert und zur Laufzeit in der Express-Anwendung geladen werden, um Tokens damit zu signieren.
Warum 32 Byte Secret-Länge?
Wir generieren hier ein 32 Byte langes Secret, was 256 Bit Entropie entspricht. Nach offizieller Empfehlung (RFC 7518, Seite 7) stellt dies einen sicheren Mindeststandard für HMAC SHA-256 (HS256) dar – dem Standardalgorithmus zur Signierung von JWTs.
Ein kürzeres Secret birgt ein hohes Risiko: Es kann durch Brute-Force-Angriffe geknackt werden – wie unter anderem dieser Beitrag von Auth0 verdeutlicht: Brute-Forcing a HS256 JSON Web Token
2. Token-Erstellung
Nach erfolgreicher Authentifizierung eines Nutzers (in unserem Fall nach erfolgreicher Login-Anfrage) erstellen wir das JWT wie folgt:
const token = jwt.sign(
{ success: data.success },
secretKey,
{ expiresIn: '4h' }
);
Hier erzeugen wir ein JWT mit einem benutzerdefinierten Payload (z. B. { success: true }) und einer Ablaufzeit von 4 Stunden. Nach diesem Zeitraum ist das Token ungültig – der Nutzer muss sich dann neu authentifizieren.
Empfehlung zur Gültigkeitsdauer:
Die Lebensdauer eines Tokens sollte immer auf die Anforderungen und Sicherheitsbedürfnisse deines Projekts abgestimmt werden.
Grundsatz: Je kürzer die Gültigkeit, desto geringer das Risiko, dass ein gestohlenes Token missbraucht werden kann.
Allerdings muss dabei auch der Nutzungskomfort berücksichtigt werden:
Ein zu kurzlebiges Token kann dazu führen, dass sich Nutzer sehr häufig neu anmelden müssen – was insbesondere in Anwendungen mit längerer Nutzungsdauer zu Frustration führen kann. Hier empfiehlt es sich, je nach Szenario einen Refresh-Token-Mechanismus zu implementieren, was wir in diesem Beispiel aber nicht betrachten.
Zum Abschluss wird das erzeugte Token an die Antwort der Login-Anfrage angehängt, sodass unser Angular-Client es empfangen und für zukünftige API-Requests verwenden kann.
Wichtig zu wissen: JWT = signiert, aber nicht verschlüsselt
Ein JSON Web Token (JWT) wird signiert, um Manipulationen zu verhindern – es garantiert die Integrität der Daten.
Aber: Der Inhalt des Tokens (Header & Payload) ist nicht verschlüsselt, sondern nur Base64URL-codiert und für jeden lesbar, der das Token in die Hände bekommt.
Daher gilt: Immer nur nicht-sensible und minimale Daten im Payload speichern!
JWT-Handling in Angular – Speicherung und Absicherung von API-Requests
Nachdem unser Node.js-Backend das JWT erfolgreich ausgestellt und an das Angular-Frontend übermittelt hat, müssen wir das Token auf der Client-Seite speichern und bei API-Requests mitsenden.
1. Token empfangen und sicher speichern
Nach erfolgreichem Login empfängt Angular das JWT vom Backend in der HTTP-Response. In unserem Beispiel nutzen wir für die Speicherung den sessionStorage:
sessionStorage.setItem('token', response['token']);
Warum sessionStorage?
Das JWT wird im Angular-Client im sessionStorage gespeichert, da es dort nur so lange erhalten bleibt, wie der aktuelle Browser-Tab geöffnet ist. Wird der Tab geschlossen, wird das Token automatisch gelöscht. Das macht sessionStorage besonders geeignet für kurzlebige Sessions und reduziert gleichzeitig das Risiko, dass ein Angreifer über einen Cross-Site-Scripting-(XSS)-Angriff dauerhaft auf das Token zugreifen kann.
Im Vergleich zum localStorage, der tab- und sitzungsübergreifend speichert, bietet sessionStorage damit einen sinnvollen Kompromiss zwischen Sicherheit und einfacher Handhabung. Diese Entscheidung kann aber je nach Anwendungsfall variiert werden.
2. Token im Authorization Header mitsenden
Um uns gegenüber dem Backend zu authentifizieren, müssen wir das JWT bei jedem geschützten API-Request im HTTP-Header mitsenden – im Standardformat:
Authorization: Bearer <JWT-TOKEN>
Dafür greifen wir einfach auf das gespeicherte Token zurück:
const authHeader = new HttpHeaders({
'Authorization': `Bearer ${sessionStorage.getItem('token')}`
});
this.http.get('/api/protected-endpoint', { headers: authHeader })
.subscribe(response => {
});
Token-Verifizierung im Node.js/Express-Backend
Nachdem das Angular-Frontend das JWT korrekt mitsendet, muss unser Backend nun bei jedem geschützten API-Aufruf prüfen, ob das Token gültig ist und korrekt signiert wurde. Nur dann darf der Request weiterverarbeitet werden.
1. Middleware zur Verifizierung des JWT
In Express erstellen wir nun eine zentralen Middleware, mit der alle geschützten Routen absichert werden können.
function authenticateJWT(req, res, next) {
const authHeader = req.headers.authorization;
if (authHeader) {
const token = authHeader.split(' ')[1];
jwt.verify(token, secretKey, (err, decoded) => {
if (err) {
return res.sendStatus(403);
}
next();
});
} else {
res.sendStatus(401);
}
}
Zunächst überprüfen wir, ob ein Authorization-Header vorhanden ist. Ist das der Fall, wird das JWT aus dem Header extrahiert und mit jwt.verify() anhand des hinterlegten Secrets validiert. Ist das Token gültig und nicht abgelaufen, wird der Request an die nächste Middleware oder Route weitergereicht. Im Fehlerfall – etwa bei abgelaufenem oder manipuliertem Token – antwortet der Server mit dem Statuscode 403 Forbidden. Wird gar kein Token übermittelt, erfolgt eine 401 Unauthorized-Antwort.
2. Einsatz der Middleware
Die eben erstellte Middleware lassen wir jetzt für alle geschützten API-Routen greift, klammern aber gezielt bestimmte Endpunkte (wie den Login) aus:
app.use('protected-endpoint', (req, res, next) => {
if (req.originalUrl === '/api/login') {
next();
} else {
authenticateJWT(req, res, next);
}
});
In diesem Beispiel wird die Middleware für alle Routen mit dem Pfad protected-endpoint verwendet – mit Ausnahme der Login-Route. Dadurch wird die Authentifizierung nur dort geprüft, wo sie tatsächlich erforderlich ist, während der Login weiterhin möglich bleibt, da das Token erst an dieser Stelle erzeugt wird.
Einfache JWT-Absicherung als pragmatische Lösung
Mit der Absicherung basierend auf JSON Web Token, wie in diesem Beitrag gezeigt, lässt sich die Kommunikation zwischen einem Angular-Frontend und einer Node.js-Express-Middleware schnell, pragmatisch und zuverlässig umsetzen – ohne dass von Anfang an komplexe Identity-Lösungen wie Keycloak erforderlich sind.
Diese Methode bietet eine Reihe von Vorteilen: Die Implementierung bleibt überschaubar und lässt sich mit wenigen Codezeilen realisieren. Gleichzeitig ermöglicht sie eine klare Trennung zwischen öffentlichen und geschützten API-Endpunkten und lässt sich nahtlos in bestehende Projekte integrieren – ideal für Prototypen, MVPs oder kleinere Anwendungen.
Gerade in der frühen Projektphase ist ein JWT-Setup häufig völlig ausreichend, um unautorisierte Zugriffe zu verhindern, Sessions kontrolliert zu verwalten und bereits erste Schutzmechanismen in der Anwendung zu etablieren.
Dabei gilt es jedoch zu beachten:
Die Absicherung mittels JWT stellt nur einen Teilaspekt einer ganzheitlichen Sicherheitsarchitektur dar. Weitere Maßnahmen wie Transportverschlüsselung (HTTPS), eine sichere CORS-Konfiguration und Schutz vor Cross-Site Scripting (XSS, um nur ein paar zu nennen) sind ebenso essenziell, um moderne Webanwendungen wirksam zu schützen.
Richtig eingesetzt ist JWT ein solides Fundament, auf dem sich weiter aufbauen lässt.