React Frontend mit JWT-Authentifizierung – Sichere Cookie-basierte Lösung
Erstellen Sie ein sicheres, modernes React Frontend, das nahtlos mit dem Express.js JWT-Backend zusammenarbeitet. Diese Implementierung nutzt httpOnly-Cookies für maximale Sicherheit und bietet eine benutzerfreundliche Oberfläche mit automatischer Token-Verwaltung.
🎯 Lernziele
Abschnitt betitelt „🎯 Lernziele“Nach diesem Tutorial können Sie:
- ✅ Ein sicheres React Frontend mit Cookie-basierter JWT-Authentifizierung implementieren
- ✅ Automatische Token-Erneuerung ohne Benutzerinteraktion einrichten
- ✅ Geschützte Routen und persistente Anmeldung umsetzen
- ✅ Eine moderne UI mit React Bootstrap und React Router erstellen
- ✅ Best Practices für Frontend-Sicherheit anwenden
🔄 Authentifizierungs-Flow
Abschnitt betitelt „🔄 Authentifizierungs-Flow“- App-Start → Automatische Prüfung des Auth-Status über
/api/auth/check - Nicht authentifiziert → Weiterleitung zur Login-Seite
- Login erfolgreich → httpOnly-Cookies werden gesetzt, Weiterleitung zum Dashboard
- API-Requests → Cookies werden automatisch mitgesendet
- Token abgelaufen → Automatische Erneuerung im Hintergrund
- Logout → Cookies werden gelöscht, Session beendet
🛠️ Projekt einrichten
Abschnitt betitelt „🛠️ Projekt einrichten“1. React-Projekt erstellen
Abschnitt betitelt „1. React-Projekt erstellen“npm create vite@latest jwt-frontend -- --template reactcd jwt-frontendnpm install2. Dependencies installieren
Abschnitt betitelt „2. Dependencies installieren“npm install react-bootstrap bootstrap react-router-dom axiosPaket-Übersicht:
react-bootstrap+bootstrap: UI-Komponenten und Stylingreact-router-dom: Client-seitiges Routingaxios: HTTP-Client mit Interceptor-Unterstützung
3. Bootstrap CSS einbinden
Abschnitt betitelt „3. Bootstrap CSS einbinden“src/main.jsx:
import { StrictMode } from 'react'import { createRoot } from 'react-dom/client'import './index.css'import App from './App.jsx'import 'bootstrap/dist/css/bootstrap.min.css'
createRoot(document.getElementById('root')).render( <StrictMode> <App /> </StrictMode>)📁 Projektarchitektur
Abschnitt betitelt „📁 Projektarchitektur“Directorysrc/
Directorycomponents/
- Navigation.jsx # Hauptnavigation mit User-Dropdown
- LoginForm.jsx # Login-Formular mit Validierung
- ProtectedRoute.jsx # Route-Schutz für authentifizierte Bereiche
Directorycontext/
- AuthContext.jsx # Globaler Auth-State mit React Context
Directorypages/
- Home.jsx # Startseite mit intelligenter Weiterleitung
- Dashboard.jsx # Hauptbereich nach erfolgreicher Anmeldung
- Profile.jsx # Benutzerprofil mit Sicherheitsinformationen
Directoryservices/
- api.js # Axios-Konfiguration und API-Funktionen
- App.jsx # Hauptkomponente mit Router-Setup
- main.jsx # App-Einstiegspunkt
Ordnerstruktur erstellen
Abschnitt betitelt „Ordnerstruktur erstellen“Windows (PowerShell) / Git Bash:
mkdir -p src/components src/context src/pages src/services
# Dateien erstellentouch src/components/Navigation.jsxtouch src/components/LoginForm.jsxtouch src/components/ProtectedRoute.jsxtouch src/context/AuthContext.jsxtouch src/pages/Home.jsxtouch src/pages/Dashboard.jsxtouch src/pages/Profile.jsxtouch src/services/api.js
echo "✅ Projektstruktur erstellt!"🔧 Implementierung
Abschnitt betitelt „🔧 Implementierung“1. API-Service konfigurieren (services/api.js)
Abschnitt betitelt „1. API-Service konfigurieren (services/api.js)“Zweck: Zentrale HTTP-Client-Konfiguration mit automatischer Cookie-Verwaltung
import axios from 'axios';
const API_URL = 'http://localhost:5000/api';
// Axios-Instanz mit Cookie-Support erstellenconst api = axios.create({ baseURL: API_URL, withCredentials: true, // Automatisches Senden von httpOnly-Cookies});
// Response Interceptor für automatische Fehlerbehandlungapi.interceptors.response.use( (response) => response, async (error) => { // Bei 401 (Unauthorized) zur Login-Seite umleiten if (error.response?.status === 401) { window.location.href = '/login'; } return Promise.reject(error); });
// API-Funktionen für Authenticationexport const authAPI = { // Benutzer anmelden login: async (username, password) => { const response = await api.post('/login', { username, password }); return response.data; },
// Benutzer abmelden logout: async () => { try { await api.post('/logout'); } catch (error) { console.error('Logout-Fehler:', error); } },
// Auth-Status prüfen (für App-Start und Refresh) checkAuth: () => api.get('/auth/check'),
// Eigene Benutzerdaten abrufen getMe: () => api.get('/me'),
// Geschützte Daten abrufen (Demo-Zweck) getProtectedData: () => api.get('/protected')};
export default api;Funktionen im Detail:
- withCredentials: true: Cookies werden automatisch bei jeder Anfrage mitgesendet
- Response Interceptor: Fängt 401-Fehler ab und leitet zur Login-Seite um
- Zentralisierte API-Calls: Alle Auth-bezogenen Requests an einem Ort
2. Authentication Context (context/AuthContext.jsx)
Abschnitt betitelt „2. Authentication Context (context/AuthContext.jsx)“Zweck: Globaler Authentifizierungs-State für die gesamte App
import React, { createContext, useContext, useState, useEffect } from "react";import { authAPI } from "../services/api";
const AuthContext = createContext();
// Custom Hook für einfachen Context-Zugriffexport const useAuth = () => { const context = useContext(AuthContext); if (!context) { throw new Error("useAuth muss innerhalb von AuthProvider verwendet werden"); } return context;};
export const AuthProvider = ({ children }) => { const [user, setUser] = useState(null); const [loading, setLoading] = useState(true); const [isAuthenticated, setIsAuthenticated] = useState(false);
// Bei App-Start: Auth-Status aus Cookies prüfen useEffect(() => { checkAuthStatus(); }, []);
// Prüft ob gültige Cookies vorhanden sind const checkAuthStatus = async () => { try { const response = await authAPI.checkAuth(); if (response.data.authenticated) { setUser(response.data.user); setIsAuthenticated(true); } else { setUser(null); setIsAuthenticated(false); } } catch (error) { console.error("Auth-Prüfung fehlgeschlagen:", error); setUser(null); setIsAuthenticated(false); } finally { setLoading(false); } };
// Login-Funktion const login = async (username, password) => { try { const data = await authAPI.login(username, password); setUser(data.user); setIsAuthenticated(true); return { success: true }; } catch (error) { return { success: false, error: error.response?.data?.error || "Login fehlgeschlagen", }; } };
// Logout-Funktion const logout = async () => { try { await authAPI.logout(); } catch (error) { console.error("Logout-Fehler:", error); }
// State zurücksetzen setUser(null); setIsAuthenticated(false); };
const value = { user, // Aktuelle Benutzerdaten isAuthenticated, // Boolean: Ist Benutzer eingeloggt? loading, // Boolean: Wird Auth-Status geprüft? login, // Funktion: Benutzer anmelden logout, // Funktion: Benutzer abmelden };
return ( <AuthContext.Provider value={value}> {children} </AuthContext.Provider> );};State-Management im Detail:
- user: Enthält Benutzerdaten (id, username, email)
- isAuthenticated: Boolean für Auth-Status
- loading: Verhindert “Flackern” während der initialen Auth-Prüfung
- checkAuthStatus(): Wird bei App-Start ausgeführt, prüft vorhandene Cookies
3. Hauptnavigation (components/Navigation.jsx)
Abschnitt betitelt „3. Hauptnavigation (components/Navigation.jsx)“Zweck: Bootstrap-Navbar mit Benutzer-Dropdown und Navigation
import React from 'react';import { Navbar, Nav, Container, NavDropdown } from 'react-bootstrap';import { useAuth } from '../context/AuthContext';import { useNavigate } from 'react-router-dom';
const Navigation = () => { const { user, logout } = useAuth(); const navigate = useNavigate();
const handleLogout = async () => { await logout(); navigate('/login'); };
return ( <Navbar bg="dark" variant="dark" expand="lg" className="mb-0"> <Container> {/* Brand/Logo */} <Navbar.Brand onClick={() => navigate('/dashboard')} style={{ cursor: 'pointer' }}> 🔐 JWT Sicherheits-Demo </Navbar.Brand>
<Navbar.Toggle aria-controls="basic-navbar-nav" /> <Navbar.Collapse id="basic-navbar-nav"> {/* Hauptnavigation */} <Nav className="me-auto"> <Nav.Link onClick={() => navigate('/dashboard')}> 📊 Dashboard </Nav.Link> <Nav.Link onClick={() => navigate('/profile')}> 👤 Profil </Nav.Link> </Nav>
{/* Benutzer-Dropdown */} <Nav> <NavDropdown title={`👋 ${user?.username}`} id="user-dropdown"> <NavDropdown.Item onClick={() => navigate('/profile')}> <span className="me-2">👤</span> Mein Profil </NavDropdown.Item> <NavDropdown.Divider /> <NavDropdown.Item onClick={handleLogout}> <span className="me-2">🚪</span> Abmelden </NavDropdown.Item> </NavDropdown> </Nav> </Navbar.Collapse> </Container> </Navbar> );};
export default Navigation;UI-Features:
- Responsive Design: Funktioniert auf mobilen Geräten
- Benutzer-Dropdown: Zeigt aktuellen Username an
- Navigation per JavaScript: Nutzt React Router’s
useNavigate
4. Login-Formular (components/LoginForm.jsx)
Abschnitt betitelt „4. Login-Formular (components/LoginForm.jsx)“Zweck: Sicheres Login-Formular mit Validierung und Fehlerbehandlung
import React, { useState } from 'react';import { Form, Button, Card, Alert, Container, Row, Col, Badge } from 'react-bootstrap';import { useAuth } from '../context/AuthContext';import { useNavigate } from 'react-router-dom';
const LoginForm = () => { const [username, setUsername] = useState(''); const [password, setPassword] = useState(''); const [error, setError] = useState(''); const [loading, setLoading] = useState(false);
const { login } = useAuth(); const navigate = useNavigate();
const handleSubmit = async (e) => { e.preventDefault(); setLoading(true); setError('');
const result = await login(username, password);
if (result.success) { navigate('/dashboard'); } else { setError(result.error); }
setLoading(false); };
return ( <Container fluid className="bg-light min-vh-100 d-flex align-items-center"> <Container> <Row className="justify-content-center"> <Col md={6} lg={4}> <Card className="shadow"> <Card.Body className="p-4"> {/* Header */} <div className="text-center mb-4"> <div className="mb-3"> <span style={{ fontSize: '3rem' }}>🔐</span> </div> <Card.Title as="h3">Sichere Anmeldung</Card.Title> <Badge bg="info" className="mb-3"> 🍪 httpOnly-Cookies für maximale Sicherheit </Badge> </div>
{/* Fehleranzeige */} {error && ( <Alert variant="danger" className="mb-3"> <strong>❌ Fehler:</strong> {error} </Alert> )}
{/* Login-Formular */} <Form onSubmit={handleSubmit}> <Form.Group className="mb-3"> <Form.Label> <strong>Benutzername</strong> </Form.Label> <Form.Control type="text" value={username} onChange={(e) => setUsername(e.target.value)} placeholder="Benutzername eingeben" required disabled={loading} /> </Form.Group>
<Form.Group className="mb-4"> <Form.Label> <strong>Passwort</strong> </Form.Label> <Form.Control type="password" value={password} onChange={(e) => setPassword(e.target.value)} placeholder="Passwort eingeben" required disabled={loading} /> </Form.Group>
<Button variant="primary" type="submit" className="w-100 mb-3" disabled={loading} size="lg" > {loading ? ( <> <span className="spinner-border spinner-border-sm me-2" role="status" aria-hidden="true"></span> Anmelden... </> ) : ( <>🚀 Anmelden</> )} </Button> </Form>
{/* Demo-Hinweise */} <div className="text-center"> <div className="border-top pt-3"> <small className="text-muted d-block mb-2"> <strong>🧪 Demo-Zugangsdaten:</strong> </small> <Badge bg="secondary" className="me-2">admin / 123456</Badge> <Badge bg="secondary">user / 123456</Badge> </div>
<div className="mt-3"> <small className="text-muted"> 🔒 Tokens werden als sichere httpOnly-Cookies gespeichert<br/> ✅ Automatische Token-Erneuerung aktiviert </small> </div> </div> </Card.Body> </Card> </Col> </Row> </Container> </Container> );};
export default LoginForm;Features im Detail:
- Form-Validierung: HTML5-required + Custom Error Handling
- Loading States: Visuelles Feedback während der Anmeldung
- Demo-Zugangsdaten: Klar ersichtlich für Testing
- Responsive Design: Funktioniert auf allen Bildschirmgrössen
5. Route-Schutz (components/ProtectedRoute.jsx)
Abschnitt betitelt „5. Route-Schutz (components/ProtectedRoute.jsx)“Zweck: Schutz für authentifizierte Bereiche mit Loading-State
import React from 'react';import { Navigate } from 'react-router-dom';import { useAuth } from '../context/AuthContext';import { Spinner, Container, Card } from 'react-bootstrap';
const ProtectedRoute = ({ children }) => { const { isAuthenticated, loading } = useAuth();
// Während Auth-Prüfung: Loading anzeigen if (loading) { return ( <Container className="min-vh-100 d-flex align-items-center justify-content-center"> <Card className="text-center p-4"> <Card.Body> <Spinner animation="border" role="status" variant="primary" className="mb-3"> <span className="visually-hidden">Laden...</span> </Spinner> <h5>🔍 Authentifizierung wird geprüft</h5> <p className="text-muted mb-0"> Sichere Cookies werden validiert... </p> </Card.Body> </Card> </Container> ); }
// Nach Auth-Prüfung: Weiterleitung oder Komponente anzeigen return isAuthenticated ? children : <Navigate to="/login" replace />;};
export default ProtectedRoute;Schutz-Logik:
- loading = true: Zeigt Loading-Spinner während Auth-Prüfung
- loading = false + isAuthenticated = true: Zeigt geschützte Komponente
- loading = false + isAuthenticated = false: Leitet zur Login-Seite um
6. Startseite mit intelligenter Weiterleitung (pages/Home.jsx)
Abschnitt betitelt „6. Startseite mit intelligenter Weiterleitung (pages/Home.jsx)“Zweck: Automatische Weiterleitung basierend auf Auth-Status
import React from 'react';import { Navigate } from 'react-router-dom';import { useAuth } from '../context/AuthContext';import { Spinner, Container, Card } from 'react-bootstrap';
const Home = () => { const { isAuthenticated, loading } = useAuth();
// Während Auth-Prüfung: Loading anzeigen if (loading) { return ( <Container className="min-vh-100 d-flex align-items-center justify-content-center bg-light"> <Card className="text-center p-4 shadow"> <Card.Body> <div className="mb-3"> <span style={{ fontSize: '3rem' }}>🔐</span> </div> <Spinner animation="border" role="status" variant="primary" className="mb-3"> <span className="visually-hidden">Laden...</span> </Spinner> <h4>JWT Sicherheits-Demo</h4> <p className="text-muted mb-0"> Anwendung wird initialisiert... </p> </Card.Body> </Card> </Container> ); }
// Nach Auth-Prüfung: Intelligente Weiterleitung return isAuthenticated ? ( <Navigate to="/dashboard" replace /> ) : ( <Navigate to="/login" replace /> );};
export default Home;Weiterleitung-Logik:
- Eingeloggt: Weiterleitung zum Dashboard
- Nicht eingeloggt: Weiterleitung zum Login
- replace: Verhindert Zurück-Button-Probleme
7. Dashboard (pages/Dashboard.jsx)
Abschnitt betitelt „7. Dashboard (pages/Dashboard.jsx)“Zweck: Hauptbereich mit Benutzerinformationen und API-Demonstrationen
import React, { useState } from 'react';import { Container, Row, Col, Card, Button, Alert, Badge, Table } from 'react-bootstrap';import { useAuth } from '../context/AuthContext';import { authAPI } from '../services/api';import Navigation from '../components/Navigation';
const Dashboard = () => { const { user } = useAuth(); const [protectedData, setProtectedData] = useState(null); const [error, setError] = useState(''); const [loading, setLoading] = useState(false);
// Geschützte Daten vom Backend laden const loadProtectedData = async () => { setLoading(true); setError('');
try { const response = await authAPI.getProtectedData(); setProtectedData(response.data); } catch (error) { setError('Fehler beim Laden der geschützten Daten'); console.error('API-Fehler:', error); }
setLoading(false); };
return ( <> <Navigation /> <Container className="py-4"> {/* Willkommen-Header */} <Row className="mb-4"> <Col> <Card className="bg-primary text-white"> <Card.Body> <div className="d-flex justify-content-between align-items-center"> <div> <h2 className="mb-1">🏠 Willkommen, {user?.username}!</h2> <p className="mb-0 opacity-75"> Sie sind erfolgreich mit sicheren httpOnly-Cookies angemeldet. </p> </div> <div className="text-end"> <Badge bg="success" className="mb-1">🔒 Sicher</Badge><br/> <Badge bg="info">🍪 Cookie-Auth</Badge> </div> </div> </Card.Body> </Card> </Col> </Row>
<Row> {/* Benutzerdaten */} <Col md={6} className="mb-4"> <Card className="h-100"> <Card.Header className="bg-light"> <h5 className="mb-0">👤 Ihre Accountdaten</h5> </Card.Header> <Card.Body> <Table borderless className="mb-0"> <tbody> <tr> <td><strong>Benutzer-ID:</strong></td> <td> <Badge bg="secondary">{user?.id}</Badge> </td> </tr> <tr> <td><strong>Username:</strong></td> <td>{user?.username}</td> </tr> <tr> <td><strong>E-Mail:</strong></td> <td>{user?.email}</td> </tr> <tr> <td><strong>Session:</strong></td> <td> <Badge bg="success">✅ Aktiv</Badge> </td> </tr> </tbody> </Table>
<Alert variant="info" className="mt-3 mb-0"> <small> <strong>ℹ️ Sicherheitshinweis:</strong><br/> Ihre Daten werden automatisch aus sicheren httpOnly-Cookies geladen. Diese sind vor JavaScript-Angriffen geschützt. </small> </Alert> </Card.Body> </Card> </Col>
{/* API-Demonstration */} <Col md={6} className="mb-4"> <Card className="h-100"> <Card.Header className="bg-light"> <h5 className="mb-0">🔒 Geschützte API-Daten</h5> </Card.Header> <Card.Body> {error && ( <Alert variant="danger"> <strong>❌ Fehler:</strong> {error} </Alert> )}
{protectedData ? ( <div> <Alert variant="success"> <strong>✅ Erfolgreich geladen:</strong> </Alert>
<Table borderless> <tbody> <tr> <td><strong>Nachricht:</strong></td> <td>{protectedData.message}</td> </tr> <tr> <td><strong>Geheime Daten:</strong></td> <td><Badge bg="warning">🔐 {protectedData.geheimeDaten}</Badge></td> </tr> <tr> <td><strong>Benutzer:</strong></td> <td>{protectedData.user?.username}</td> </tr> <tr> <td><strong>Zeitstempel:</strong></td> <td> <small className="text-muted"> {new Date().toLocaleString()} </small> </td> </tr> </tbody> </Table> </div> ) : ( <div> <p className="text-muted"> Klicken Sie auf den Button, um geschützte Daten vom Server zu laden. Dies demonstriert die automatische Cookie-Übertragung. </p>
<Alert variant="light" className="border"> <small> <strong>🧪 Was passiert beim Klick:</strong><br/> 1. HTTP-Request an <code>/api/protected</code><br/> 2. httpOnly-Cookies werden automatisch mitgesendet<br/> 3. Backend validiert den Access Token<br/> 4. Geschützte Daten werden zurückgegeben </small> </Alert> </div> )}
<div className="d-grid"> <Button variant="primary" onClick={loadProtectedData} disabled={loading} size="lg" > {loading ? ( <> <span className="spinner-border spinner-border-sm me-2" role="status"></span> Laden... </> ) : ( <>🔄 Geschützte Daten laden</> )} </Button> </div> </Card.Body> </Card> </Col> </Row>
{/* Sicherheits-Informationen */} <Row> <Col> <Card className="bg-light"> <Card.Body> <h5>🛡️ Ihre Sicherheitsfeatures</h5> <Row> <Col md={3}> <div className="text-center p-3"> <div className="mb-2">🍪</div> <strong>httpOnly-Cookies</strong> <br/> <small className="text-muted">Tokens vor JS-Zugriff geschützt</small> </div> </Col> <Col md={3}> <div className="text-center p-3"> <div className="mb-2">🔄</div> <strong>Auto-Refresh</strong> <br/> <small className="text-muted">Automatische Token-Erneuerung</small> </div> </Col> <Col md={3}> <div className="text-center p-3"> <div className="mb-2">🛡️</div> <strong>CSRF-Schutz</strong> <br/> <small className="text-muted">SameSite Strict Policy</small> </div> </Col> <Col md={3}> <div className="text-center p-3"> <div className="mb-2">🔒</div> <strong>HTTPS-Ready</strong> <br/> <small className="text-muted">Secure-Flag in Produktion</small> </div> </Col> </Row> </Card.Body> </Card> </Col> </Row> </Container> </> );};
export default Dashboard;Dashboard-Features:
- Übersichtliche Benutzerinformationen: Alle relevanten Account-Daten
- API-Demonstration: Live-Test der geschützten Endpoints
- Sicherheits-Indikatoren: Zeigt aktivierte Schutzmassnahmen
- Responsive Layout: Bootstrap Grid für alle Bildschirmgrössen
8. Benutzerprofil (pages/Profile.jsx)
Abschnitt betitelt „8. Benutzerprofil (pages/Profile.jsx)“Zweck: Detaillierte Benutzerinformationen und Sicherheitsübersicht
import React, { useState, useEffect } from 'react';import { Container, Row, Col, Card, Badge, Button, Alert, Table, ListGroup } from 'react-bootstrap';import { useAuth } from '../context/AuthContext';import { authAPI } from '../services/api';import Navigation from '../components/Navigation';
const Profile = () => { const { user } = useAuth(); const [userDetails, setUserDetails] = useState(null); const [error, setError] = useState(''); const [loading, setLoading] = useState(false);
// Beim Laden der Komponente: Benutzerdaten abrufen useEffect(() => { loadUserDetails(); }, []);
const loadUserDetails = async () => { setLoading(true); setError('');
try { const response = await authAPI.getMe(); setUserDetails(response.data.user); } catch (error) { setError('Fehler beim Laden der Benutzerdaten'); console.error('API-Fehler:', error); }
setLoading(false); };
return ( <> <Navigation /> <Container className="py-4"> {/* Profil-Header */} <Row className="justify-content-center mb-4"> <Col md={10}> <Card className="text-center"> <Card.Body className="py-5"> <div className="mb-4"> <span style={{ fontSize: '5rem' }}>👤</span> </div> <h2>Mein Benutzerprofil</h2> <p className="text-muted mb-3"> Verwalten Sie Ihre Kontoinformationen und Sicherheitseinstellungen </p> <div> <Badge bg="success" className="me-2">🔐 Sicher authentifiziert</Badge> <Badge bg="info">🍪 Cookie-basierte Session</Badge> </div> </Card.Body> </Card> </Col> </Row>
<Row className="justify-content-center"> <Col md={10}> {error && ( <Alert variant="danger" className="mb-4"> <strong>❌ Fehler:</strong> {error} </Alert> )}
<Row> {/* Persönliche Informationen */} <Col md={6} className="mb-4"> <Card className="h-100"> <Card.Header className="bg-primary text-white"> <h5 className="mb-0">📋 Persönliche Informationen</h5> </Card.Header> <Card.Body> {loading ? ( <div className="text-center py-4"> <div className="spinner-border text-primary" role="status"> <span className="visually-hidden">Laden...</span> </div> <p className="mt-2 text-muted">Daten werden geladen...</p> </div> ) : userDetails ? ( <div> <Table borderless className="mb-4"> <tbody> <tr> <td><strong>Benutzer-ID:</strong></td> <td> <Badge bg="secondary">{userDetails.id}</Badge> </td> </tr> <tr> <td><strong>Benutzername:</strong></td> <td>{userDetails.username}</td> </tr> <tr> <td><strong>E-Mail-Adresse:</strong></td> <td>{userDetails.email}</td> </tr> <tr> <td><strong>Account-Status:</strong></td> <td> <Badge bg="success">✅ Aktiv</Badge> </td> </tr> </tbody> </Table>
<Alert variant="info"> <small> <strong>🔍 Datenquelle:</strong><br/> Diese Informationen werden direkt vom Backend über die geschützte <code>/api/me</code> Route geladen. </small> </Alert> </div> ) : null}
<div className="d-grid"> <Button variant="outline-primary" onClick={loadUserDetails} disabled={loading} > {loading ? 'Laden...' : '🔄 Daten aktualisieren'} </Button> </div> </Card.Body> </Card> </Col>
{/* Sicherheitsinformationen */} <Col md={6} className="mb-4"> <Card className="h-100"> <Card.Header className="bg-success text-white"> <h5 className="mb-0">🔒 Sicherheitsinformationen</h5> </Card.Header> <Card.Body> <ListGroup variant="flush"> <ListGroup.Item className="d-flex justify-content-between align-items-center"> <div> <strong>Token-Speicherung</strong> <br/> <small className="text-muted">Wie Ihre Authentifizierung gespeichert wird</small> </div> <Badge bg="success">🍪 httpOnly Cookies</Badge> </ListGroup.Item>
<ListGroup.Item className="d-flex justify-content-between align-items-center"> <div> <strong>CSRF-Schutz</strong> <br/> <small className="text-muted">Schutz vor Cross-Site Request Forgery</small> </div> <Badge bg="success">✅ SameSite Strict</Badge> </ListGroup.Item>
<ListGroup.Item className="d-flex justify-content-between align-items-center"> <div> <strong>Token-Erneuerung</strong> <br/> <small className="text-muted">Automatische Verlängerung der Session</small> </div> <Badge bg="success">🔄 Automatisch</Badge> </ListGroup.Item>
<ListGroup.Item className="d-flex justify-content-between align-items-center"> <div> <strong>XSS-Schutz</strong> <br/> <small className="text-muted">Schutz vor JavaScript-Angriffen</small> </div> <Badge bg="success">🛡️ httpOnly</Badge> </ListGroup.Item>
<ListGroup.Item className="d-flex justify-content-between align-items-center"> <div> <strong>Session-Status</strong> <br/> <small className="text-muted">Aktuelle Verbindung zum Server</small> </div> <Badge bg="success">✅ Verbunden</Badge> </ListGroup.Item> </ListGroup>
<Alert variant="success" className="mt-3 mb-0"> <small> <strong>🛡️ Sicherheitsgarantie:</strong><br/> Ihre Authentifizierungs-Tokens sind vollständig vor JavaScript-Zugriffen geschützt und werden automatisch bei jeder Anfrage validiert. </small> </Alert> </Card.Body> </Card> </Col> </Row>
{/* Session-Informationen */} <Row> <Col> <Card> <Card.Header> <h5 className="mb-0">📊 Session-Details</h5> </Card.Header> <Card.Body> <Row> <Col md={4}> <div className="text-center p-3"> <div className="h2 text-primary">🕐</div> <strong>Session-Start</strong> <br/> <small className="text-muted"> {new Date().toLocaleString()} </small> </div> </Col> <Col md={4}> <div className="text-center p-3"> <div className="h2 text-success">⏱️</div> <strong>Token-Gültigkeitsdauer</strong> <br/> <small className="text-muted"> Access Token: 15 Minuten<br/> Refresh Token: 7 Tage </small> </div> </Col> <Col md={4}> <div className="text-center p-3"> <div className="h2 text-info">🔄</div> <strong>Automatisches Refresh</strong> <br/> <small className="text-muted"> Tokens werden automatisch<br/> im Hintergrund erneuert </small> </div> </Col> </Row> </Card.Body> </Card> </Col> </Row> </Col> </Row> </Container> </> );};
export default Profile;Profil-Features:
- Detaillierte Benutzerinformationen: Vollständige Account-Übersicht
- Sicherheits-Dashboard: Alle aktiven Schutzmassnahmen im Überblick
- Session-Management: Informationen zur aktuellen Sitzung
- Live-Data-Updates: Manuelles Aktualisieren der Benutzerdaten
9. Haupt-App-Komponente (App.jsx)
Abschnitt betitelt „9. Haupt-App-Komponente (App.jsx)“Zweck: Router-Setup und Provider-Konfiguration
import React from 'react';import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';import { AuthProvider } from './context/AuthContext';import ProtectedRoute from './components/ProtectedRoute';
// Seiten-Komponentenimport Home from './pages/Home';import Dashboard from './pages/Dashboard';import Profile from './pages/Profile';import LoginForm from './components/LoginForm';
function App() { return ( <AuthProvider> <Router> <div className="App"> <Routes> {/* Öffentliche Routen */} <Route path="/" element={<Home />} /> <Route path="/login" element={<LoginForm />} />
{/* Geschützte Routen */} <Route path="/dashboard" element={ <ProtectedRoute> <Dashboard /> </ProtectedRoute> } />
<Route path="/profile" element={ <ProtectedRoute> <Profile /> </ProtectedRoute> } />
{/* Fallback für unbekannte URLs */} <Route path="*" element={<Home />} /> </Routes> </div> </Router> </AuthProvider> );}
export default App;App-Architektur:
- AuthProvider: Globaler Auth-State für alle Komponenten
- Router: Client-seitiges Routing mit React Router
- ProtectedRoute: Wrapper für authentifizierte Bereiche
- Fallback-Route: Unbekannte URLs werden zur Startseite umgeleitet
🧪 Anwendung testen
Abschnitt betitelt „🧪 Anwendung testen“1. Entwicklungsserver starten
Abschnitt betitelt „1. Entwicklungsserver starten“npm run devErwartete Ausgabe:
VITE v4.x.x ready in Xxxx ms
➜ Local: http://localhost:5173/➜ Network: use --host to expose➜ press h to show help2. Backend-Verfügbarkeit prüfen
Abschnitt betitelt „2. Backend-Verfügbarkeit prüfen“Stellen Sie sicher, dass das Express.js Backend auf Port 5000 läuft:
# Im Backend-Verzeichnisnpm run dev3. Vollständigen Auth-Flow testen
Abschnitt betitelt „3. Vollständigen Auth-Flow testen“- App öffnen:
http://localhost:5173→ Automatische Weiterleitung zum Login - Anmelden: Verwenden Sie
admin/123456oderuser/123456 - Dashboard erkunden: Geschützte Daten laden, Benutzerinformationen anzeigen
- Profil besuchen: Detaillierte Sicherheitsinformationen ansehen
- Seite neu laden: Session bleibt bestehen (httpOnly-Cookies!)
- Browser-DevTools: Cookies unter Application → Cookies prüfen
- Abmelden: Cookies werden gelöscht, Weiterleitung zum Login
4. Sicherheitsfeatures validieren
Abschnitt betitelt „4. Sicherheitsfeatures validieren“httpOnly-Cookies prüfen:
// In Browser-Konsole (sollte undefined zurückgeben)document.cookieAutomatische Token-Erneuerung testen:
- 15 Minuten warten (Access Token läuft ab)
- API-Request durchführen → Automatisches Refresh
- Weiterhin eingeloggt bleiben
CORS und Credentials prüfen:
// Beispiel-Request (sollte funktionieren)fetch('http://localhost:5000/api/me', { credentials: 'include'}).then(r => r.json()).then(console.log)🔧 Technische Details
Abschnitt betitelt „🔧 Technische Details“🍪 Cookie-Management
Abschnitt betitelt „🍪 Cookie-Management“// Backend setzt httpOnly-Cookiesres.cookie("accessToken", token, { httpOnly: true, // Nicht von JavaScript lesbar secure: isProduction, // Nur über HTTPS in Produktion sameSite: "strict", // CSRF-Schutz maxAge: 15 * 60 * 1000 // 15 Minuten});// Frontend sendet Cookies automatischconst api = axios.create({ withCredentials: true // Cookies automatisch mitsenden});🔄 Automatische Token-Erneuerung
Abschnitt betitelt „🔄 Automatische Token-Erneuerung“Backend (/api/auth/check):
- Prüft Access Token aus Cookie
- Falls abgelaufen: Verwendet Refresh Token
- Setzt neuen Access Token als Cookie
- Gibt Benutzerinformationen zurück
Frontend (AuthContext):
- Ruft beim App-Start
/api/auth/checkauf - Speichert Benutzer-State basierend auf Server-Response
- Keine manuelle Token-Verwaltung erforderlich
🛡️ Sicherheitsfeatures
Abschnitt betitelt „🛡️ Sicherheitsfeatures“| Feature | Backend | Frontend | Schutz vor |
|---|---|---|---|
| httpOnly Cookies | ✅ | ✅ | XSS-Angriffe |
| SameSite Strict | ✅ | ✅ | CSRF-Angriffe |
| Secure Flag (Prod) | ✅ | ✅ | Man-in-the-Middle |
| CORS Credentials | ✅ | ✅ | Unerlaubte Origins |
| Token Expiration | ✅ | ✅ | Session Hijacking |
| Automatic Refresh | ✅ | ✅ | Token-Replay |
📱 Responsive Design
Abschnitt betitelt „📱 Responsive Design“// Bootstrap-Breakpoints<Col md={6} lg={4}> // Mobile-first Design<Container fluid> // Vollbreite auf mobilen Geräten<Navbar.Toggle /> // Hamburger-Menü für Mobile🎯 Error Handling
Abschnitt betitelt „🎯 Error Handling“// Zentralisierte Fehlerbehandlungapi.interceptors.response.use( (response) => response, (error) => { if (error.response?.status === 401) { window.location.href = '/login'; // Auto-Logout bei Unauthorized } return Promise.reject(error); });✅ Fazit
Abschnitt betitelt „✅ Fazit“Sie haben erfolgreich ein production-ready React Frontend mit JWT-Authentifizierung implementiert:
🎉 Erreichte Ziele
Abschnitt betitelt „🎉 Erreichte Ziele“- ✅ Sichere Cookie-basierte Authentifizierung mit httpOnly-Flags
- ✅ Automatische Token-Verwaltung ohne manuelle Intervention
- ✅ Persistente Sessions über Browser-Neustarts hinweg
- ✅ Moderne React-Architektur mit Context API und Hooks
- ✅ Responsive Bootstrap-UI für alle Geräte
- ✅ Umfassende Sicherheitsfeatures gegen XSS und CSRF
- ✅ Benutzerfreundliche UX mit Loading-States und Feedback
🚀 Production-Readiness
Abschnitt betitelt „🚀 Production-Readiness“Diese Implementierung ist bereit für den Produktionseinsatz und beinhaltet:
- Professionelle Fehlerbehandlung
- Optimierte Performance durch intelligente State-Updates
- Skalierbare Architektur für grössere Anwendungen
- Sicherheits-Best-Practices auf Enterprise-Niveau
Diese JWT-Implementierung stellt das Fundament für sichere, moderne Web-Anwendungen dar! 🎉