Express.js Backend mit JWT
In diesem Kapitel erstellen wir ein funktionierendes Express.js Backend mit JWT-Authentifizierung. Das Besondere: Alles in einer einzigen Datei! Perfekt für Einsteiger, die verstehen möchten, wie JWT in der Praxis funktioniert.
Was Sie nach diesem Tutorial können:
Abschnitt betitelt „Was Sie nach diesem Tutorial können:“- ✅ Ein Express.js Server mit JWT-Authentifizierung aufsetzen
- ✅ Login- und Logout-Funktionen implementieren
- ✅ Geschützte API-Routen erstellen
- ✅ Access- und Refresh-Tokens verwalten
- ✅ Die Grundlagen für jede moderne Web-Anwendung verstehen
Was macht unser Backend?
Abschnitt betitelt „Was macht unser Backend?“Stellen Sie sich vor, Sie bauen eine Web-App:
- Benutzer meldet sich an → Backend gibt JWT-Token aus
- Benutzer macht Anfragen → Backend prüft Token bei jeder Anfrage
- Token läuft ab → Backend erneuert Token automatisch
- Benutzer meldet sich ab → Backend invalidiert Token
1. Projekt erstellen
Abschnitt betitelt „1. Projekt erstellen“mkdir jwt-backendcd jwt-backendnpm init -y2. Pakete installieren
Abschnitt betitelt „2. Pakete installieren“npm install express jsonwebtoken bcryptjs cors cookie-parsernpm install -D nodemon3. package.json anpassen
Abschnitt betitelt „3. package.json anpassen“{ "name": "jwt-backend", "type": "module", "scripts": { "dev": "nodemon server.js" }}📄 Eine einzige Datei: server.js
Abschnitt betitelt „📄 Eine einzige Datei: server.js“import express from "express";import jwt from "jsonwebtoken";import bcrypt from "bcryptjs";import cors from "cors";import cookieParser from "cookie-parser";
const app = express();const PORT = 5000;
// Produktionsmodus erkennenconst isProduction = process.env.NODE_ENV === "production";
// Secret-Schlüssel (in echter App: aus .env oder über Umgebungsvariable!)const JWT_SECRET = "mein-geheimer-schluessel-123";
// Middlewareapp.use(express.json());app.use(cookieParser());
// CORS konfigurierenapp.use( cors({ origin: isProduction ? "https://ihre-domain.com" : "http://localhost:5173", credentials: true, }));
// Fake Benutzer-Datenbankconst users = [ { id: 1, username: "admin", email: "admin@test.de", // Passwort: "123456" password: "$2b$10$qNz0LLp7aMvp8BYWUzYHxOw.YV53abp5sr4tcOvJyd3Ll2RVv.ecK", }, { id: 2, username: "user", email: "user@test.de", // Passwort: "123456" password: "$2b$10$qNz0LLp7aMvp8BYWUzYHxOw.YV53abp5sr4tcOvJyd3Ll2RVv.ecK", },];
// Gespeicherte Refresh Tokens (in Memory, produktiv: Datenbank)let refreshTokens = [];
// Hilfsfunktionenconst generateTokens = (userId) => { const accessToken = jwt.sign({ userId }, JWT_SECRET, { expiresIn: "15m" }); const refreshToken = jwt.sign({ userId }, JWT_SECRET, { expiresIn: "7d" }); return { accessToken, refreshToken };};
const findUserByUsername = (username) => users.find((user) => user.username === username);const findUserById = (id) => users.find((user) => user.id === id);
const getCookieOptions = (maxAge) => ({ httpOnly: true, secure: isProduction, sameSite: "strict", maxAge, path: "/",});
// Token-Prüfung Middlewareconst authenticateToken = (req, res, next) => { const token = req.cookies.accessToken; if (!token) return res.status(401).json({ error: "Token fehlt" });
jwt.verify(token, JWT_SECRET, (err, decoded) => { if (err) return res.status(403).json({ error: "Token ungültig" }); const user = findUserById(decoded.userId); if (!user) return res.status(403).json({ error: "Benutzer nicht gefunden" }); req.user = { id: user.id, username: user.username, email: user.email }; next(); });};
// ==========================// API-Routen// ==========================
// LOGINapp.post("/api/login", async (req, res) => { const { username, password } = req.body; if (!username || !password) return res .status(400) .json({ error: "Username und Passwort erforderlich" });
const user = findUserByUsername(username); if (!user) return res.status(401).json({ error: "Falsche Anmeldedaten" });
const validPassword = await bcrypt.compare(password, user.password); if (!validPassword) return res.status(401).json({ error: "Falsche Anmeldedaten" });
const { accessToken, refreshToken } = generateTokens(user.id); refreshTokens.push(refreshToken);
res.cookie("accessToken", accessToken, getCookieOptions(15 * 60 * 1000)); res.cookie( "refreshToken", refreshToken, getCookieOptions(7 * 24 * 60 * 60 * 1000) );
res.json({ message: "Erfolgreich angemeldet", user: { id: user.id, username: user.username, email: user.email }, });});
// TOKEN ERNEUERNapp.post("/api/refresh", (req, res) => { const refreshToken = req.cookies.refreshToken || req.body.refreshToken; if (!refreshToken) return res.status(401).json({ error: "Refresh Token fehlt" }); if (!refreshTokens.includes(refreshToken)) return res.status(403).json({ error: "Refresh Token ungültig" });
jwt.verify(refreshToken, JWT_SECRET, (err, decoded) => { if (err) return res.status(403).json({ error: "Refresh Token abgelaufen" }); const newAccessToken = jwt.sign({ userId: decoded.userId }, JWT_SECRET, { expiresIn: "15m", }); res.cookie("accessToken", newAccessToken, getCookieOptions(15 * 60 * 1000)); res.json({ accessToken: newAccessToken }); });});
// LOGOUTapp.post("/api/logout", (req, res) => { const refreshToken = req.cookies.refreshToken || req.body.refreshToken; if (refreshToken) refreshTokens = refreshTokens.filter((token) => token !== refreshToken); res.clearCookie("accessToken", getCookieOptions(0)); res.clearCookie("refreshToken", getCookieOptions(0)); res.json({ message: "Erfolgreich abgemeldet" });});
// BENUTZERDATEN (AUTHENTIFIZIERT)app.get("/api/me", authenticateToken, (req, res) => { res.json({ user: req.user });});
// WEITERE GESCHÜTZTE ROUTE (BEISPIEL)app.get("/api/protected", authenticateToken, (req, res) => { res.json({ message: "Diese Route ist geschützt!", user: req.user });});
// AUTH STATUS CHECKapp.get("/api/auth/check", (req, res) => { const accessToken = req.cookies.accessToken; const refreshToken = req.cookies.refreshToken;
// Zuerst Access Token prüfen if (accessToken) { try { const decoded = jwt.verify(accessToken, JWT_SECRET); const user = findUserById(decoded.userId); if (user) return res.json({ authenticated: true, user: { id: user.id, username: user.username, email: user.email }, }); } catch {} } // Wenn Access Token abgelaufen, versuche mit Refresh if (refreshToken && refreshTokens.includes(refreshToken)) { try { const decoded = jwt.verify(refreshToken, JWT_SECRET); const user = findUserById(decoded.userId); if (user) { const newAccessToken = jwt.sign( { userId: decoded.userId }, JWT_SECRET, { expiresIn: "15m" } ); res.cookie( "accessToken", newAccessToken, getCookieOptions(15 * 60 * 1000) ); return res.json({ authenticated: true, user: { id: user.id, username: user.username, email: user.email }, }); } } catch {} } // Nicht authentifiziert, aber KEIN 401 res.json({ authenticated: false });});
// STARTSEITEapp.get("/", (req, res) => { res.json({ message: "JWT Backend läuft!", modus: isProduction ? "Produktion" : "Entwicklung", sicherheit: { cookies: "httpOnly aktiviert", secure: isProduction ? "HTTPS erforderlich" : "HTTP erlaubt (nur Entwicklung)", sameSite: "strict (CSRF-Schutz)", }, routen: { Anmelden: "POST /api/login", "Token erneuern": "POST /api/refresh", Abmelden: "POST /api/logout", "Meine Daten": "GET /api/me", Geschützt: "GET /api/protected", }, testBenutzer: { username: "admin", password: "123456", }, });});
app.listen(PORT, () => { console.log(`🚀 Server läuft auf http://localhost:${PORT}`); console.log(`🔒 Modus: ${isProduction ? "Produktion" : "Entwicklung"}`); console.log("📝 Test-Login: admin / 123456");});
// Zum Hasherzeugen für neue Passwörter (nur zum Testen):// bcrypt.hash('123456', 10).then(hash => console.log('Hash:', hash));🧪 Testen mit einfachen Beispielen
Abschnitt betitelt „🧪 Testen mit einfachen Beispielen“1. Server starten
Abschnitt betitelt „1. Server starten“npm run dev2. Mit Browser oder Postman testen
Abschnitt betitelt „2. Mit Browser oder Postman testen“Schritt 1: Anmelden
POST http://localhost:5000/api/loginContent-Type: application/json
{ "username": "admin", "password": "123456"}Antwort:
{ "message": "Erfolgreich angemeldet", "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "user": { "id": 1, "username": "admin", "email": "admin@test.de" }}Schritt 2: Geschützte Route aufrufen
GET http://localhost:5000/api/meAuthorization: Bearer IHR_ACCESS_TOKEN_HIERSchritt 3: Token erneuern
POST http://localhost:5000/api/refreshContent-Type: application/json
{ "refreshToken": "IHR_REFRESH_TOKEN_HIER"}Schritt 4: Abmelden
POST http://localhost:5000/api/logoutContent-Type: application/json
{ "refreshToken": "IHR_REFRESH_TOKEN_HIER"}📝 Was passiert hier?
Abschnitt betitelt „📝 Was passiert hier?“1. Login (/api/login)
Abschnitt betitelt „1. Login (/api/login)“- Benutzer sendet Username + Passwort
- Server prüft die Daten
- Bei Erfolg: Server erstellt 2 Tokens und sendet sie zurück
2. Geschützte Routen (/api/me, /api/protected)
Abschnitt betitelt „2. Geschützte Routen (/api/me, /api/protected)“- Benutzer sendet Access Token im Header mit
- Middleware
authenticateTokenprüft den Token - Bei gültigem Token: Route wird ausgeführt
3. Token erneuern (/api/refresh)
Abschnitt betitelt „3. Token erneuern (/api/refresh)“- Benutzer sendet Refresh Token
- Server erstellt neuen Access Token
- Benutzer kann weiter arbeiten ohne neue Anmeldung
4. Logout (/api/logout)
Abschnitt betitelt „4. Logout (/api/logout)“- Refresh Token wird aus der Server-Liste entfernt
- Token kann nicht mehr verwendet werden
✅ Fertig!
Abschnitt betitelt „✅ Fertig!“Sie haben jetzt ein funktionierendes JWT-Backend mit:
- ✅ Login/Logout
- ✅ Geschützte Routen
- ✅ Token-Erneuerung
- ✅ Benutzer-Authentifizierung
Nächster Schritt: React Frontend erstellen, das mit diesem Backend kommuniziert.
Danke für Ihr Feedback!