Authentifizierung und geschützte Routen
In diesem Kapitel implementieren wir die Authentifizierung für unsere Anwendung. Wir erstellen einen Anmeldebildschirm, einen Authentifizierungskontext und geschützte Routen, damit nur angemeldete Benutzer auf bestimmte Seiten zugreifen können. Nach Abschluss dieses Kapitels verfügt Ihre Anwendung über eine vollständige Authentifizierungslösung.
Schritt 1: Erstellen des Authentifizierungskontexts
Abschnitt betitelt „Schritt 1: Erstellen des Authentifizierungskontexts“Zuerst erstellen wir einen Authentifizierungskontext, der den Anmeldestatus des Benutzers in der gesamten Anwendung verwaltet:
import { createContext, useContext, useState, useEffect } from 'react';
const API_URL = import.meta.env.VITE_API_BASE_URL || 'http://localhost:4000';
// Create the Auth Contextconst AuthContext = createContext();
// Custom hook to use the auth contextexport function useAuth() { return useContext(AuthContext);}
export function AuthProvider({ children }) { const [currentUser, setCurrentUser] = useState(null); const [loading, setLoading] = useState(true);
// Login function const login = async (email, password) => { try { const response = await fetch(API_URL + '/auth/login', { method: 'POST', credentials: 'include', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ email, password }) });
const data = await response.json();
if (!response.ok) { throw new Error(data.message || 'Login failed'); }
// Save user data from response setCurrentUser(data.user); return data.user; } catch (error) { console.error("Login error:", error); throw error; } };
// Logout function const logout = async () => { try { await fetch(`${API_URL}/auth/logout`, { method: 'POST', credentials: 'include', // Important for cookies }); } catch (error) { console.error("Logout error:", error); }
// Clear user state regardless of server response setCurrentUser(null); };
// Check authentication status on page load useEffect(() => { const checkAuthStatus = async () => { try { const response = await fetch(`${API_URL}/auth/me`, { credentials: 'include', });
if (response.ok) { const data = await response.json(); setCurrentUser(data.user); } else { setCurrentUser(null); } } catch (error) { console.error("Auth check error:", error); setCurrentUser(null); } finally { setLoading(false); } };
checkAuthStatus(); }, []);
// Context value const value = { currentUser, login, logout, isAuthenticated: !!currentUser };
return ( <AuthContext.Provider value={value}> {!loading && children} </AuthContext.Provider> );}Schritt 2: Erstellen der geschützten Route-Komponente
Abschnitt betitelt „Schritt 2: Erstellen der geschützten Route-Komponente“Als nächstes erstellen wir eine Komponente für geschützte Routen:
import { Navigate, Outlet } from 'react-router-dom';import { useAuth } from '../contexts/AuthContext';
function ProtectedRoute() { const { currentUser } = useAuth();
// Check if user is authenticated if (!currentUser) { // Redirect them to the login page if not authenticated return <Navigate to="/login" replace />; }
// If authenticated, render the child routes return <Outlet />;}
export default ProtectedRoute;Schritt 3: Erstellen der Login-Seite
Abschnitt betitelt „Schritt 3: Erstellen der Login-Seite“Jetzt erstellen wir die Login-Seite:
import React, { useState, useEffect } from 'react';import { useAuth } from '../contexts/AuthContext';import { useNavigate } from 'react-router-dom';import { Form, Button, Card, Container, Row, Col, Alert, InputGroup } from 'react-bootstrap';import { FaEnvelope, FaLock, FaSignInAlt } from 'react-icons/fa';
function Login() { const [email, setEmail] = useState('user@example.com'); const [password, setPassword] = useState(''); const [error, setError] = useState(''); const [loading, setLoading] = useState(false); const { login, isAuthenticated } = useAuth(); const navigate = useNavigate();
// Redirect if authenticated useEffect(() => { if (isAuthenticated) { navigate('/'); } }, [isAuthenticated, navigate]);
async function handleSubmit(e) { e.preventDefault(); try { setError(''); setLoading(true); await login(email, password); navigate('/'); } catch (err) { setError('Anmeldung fehlgeschlagen: ' + (err.message || 'Unbekannter Fehler')); } finally { setLoading(false); } }
return ( <Container className="py-5"> <Row className="justify-content-center"> <Col md={8} lg={6} xl={5}> <Card className="shadow border-0 rounded-lg"> <Card.Header className="bg-primary text-white text-center py-3"> <h3 className="mb-0">Anmeldung</h3> </Card.Header> <Card.Body className="px-4 py-4"> {error && <Alert variant="danger">{error}</Alert>}
<Form onSubmit={handleSubmit}> <Form.Group className="mb-3"> <Form.Label>E-Mail-Adresse</Form.Label> <InputGroup> <InputGroup.Text> <FaEnvelope /> </InputGroup.Text> <Form.Control type="email" placeholder="E-Mail eingeben" value={email} onChange={(e) => setEmail(e.target.value)} required /> </InputGroup> </Form.Group>
<Form.Group className="mb-4"> <Form.Label>Passwort</Form.Label> <InputGroup> <InputGroup.Text> <FaLock /> </InputGroup.Text> <Form.Control type="password" placeholder="Passwort eingeben" value={password} onChange={(e) => setPassword(e.target.value)} required /> </InputGroup> </Form.Group>
<div className="d-grid gap-2"> <Button variant="primary" type="submit" disabled={loading} className="py-2" > {loading ? ( <> <span className="spinner-border spinner-border-sm me-2" role="status" aria-hidden="true"></span> Anmeldung... </> ) : ( <> <FaSignInAlt className="me-2" /> Anmelden </> )} </Button> </div> </Form> </Card.Body> </Card> </Col> </Row> </Container> );}
export default Login;Schritt 4: Aktualisieren der Navigationsleiste
Abschnitt betitelt „Schritt 4: Aktualisieren der Navigationsleiste“Aktualisieren Sie die Navbar-Komponente, um den Anmeldestatus anzuzeigen und Logout-Funktion hinzuzufügen:
import { Navbar as BootstrapNavbar, Nav, Container } from 'react-bootstrap';import { Link, NavLink } from 'react-router-dom';import { useAuth } from "../contexts/AuthContext.jsx";
const Navbar = () => { const { currentUser, isAuthenticated, logout } = useAuth();
return ( <BootstrapNavbar bg="dark" variant="dark" expand="lg"> <Container> <BootstrapNavbar.Brand as={Link} to="/">Produkt Manager</BootstrapNavbar.Brand> <BootstrapNavbar.Toggle aria-controls="basic-navbar-nav" /> <BootstrapNavbar.Collapse id="basic-navbar-nav"> <Nav className="me-auto"> {/* Only show these navigation links if user is authenticated */} {isAuthenticated && ( <> <Nav.Link as={NavLink} to="/"> Home </Nav.Link> <Nav.Link as={NavLink} to="/products"> Produkte </Nav.Link> </> )} </Nav>
<Nav> {isAuthenticated ? ( <div className="d-flex align-items-center"> <span className="text-light me-3"> {currentUser?.email || 'User'} </span> <Nav.Link onClick={logout} className="btn btn-sm" > Logout </Nav.Link> </div> ) : ( <Nav.Link as={Link} to="/login">Login</Nav.Link> )} </Nav> </BootstrapNavbar.Collapse> </Container> </BootstrapNavbar> );};
export default Navbar;Schritt 5: Aktualisieren der App.jsx mit geschützten Routen
Abschnitt betitelt „Schritt 5: Aktualisieren der App.jsx mit geschützten Routen“Aktualisieren Sie die App.jsx, um den AuthProvider zu integrieren und geschützte Routen zu konfigurieren:
import {BrowserRouter, Routes, Route} from 'react-router-dom';import Navbar from './components/Navbar';import Home from './pages/Home';import Products from './pages/Products';import {Container} from 'react-bootstrap';import Login from "./pages/Login.jsx";import {AuthProvider} from "./contexts/AuthContext";import ProtectedRoute from "./components/ProtectedRoute";
function App() { return ( <AuthProvider> <BrowserRouter> <div className="d-flex flex-column min-vh-100"> <Navbar/> <Container className="flex-grow-1 mt-4"> <Routes> {/* Public routes */} <Route path="/login" element={<Login />} />
{/* Protected routes */} <Route element={<ProtectedRoute />}> <Route path="/" element={<Home />} /> <Route path="/products" element={<Products />} /> </Route> </Routes> </Container> <footer className="bg-light py-3 mt-auto"> <Container className="text-center text-muted"> <p className="mb-0">© {new Date().getFullYear()} Produkt Manager</p> </Container> </footer> </div> </BrowserRouter> </AuthProvider> );}
export default App;Schritt 6: Testen der Authentifizierung
Abschnitt betitelt „Schritt 6: Testen der Authentifizierung“Starten Sie die Anwendung mit:
npm run devTesten Sie die Authentifizierungsfunktionen:
- Wenn Sie nicht angemeldet sind, sollten Sie zur Login-Seite weitergeleitet werden
- Versuchen Sie, sich mit den richtigen Anmeldedaten anzumelden
- Nach erfolgreicher Anmeldung sollten Sie zur Home-Seite weitergeleitet werden
- Die Navigationsleiste sollte nun Ihren Benutzernamen und einen Logout-Button anzeigen
- Testen Sie den Logout-Button, um sich abzumelden
Wenn Sie keinen Backend-Server haben, können Sie die Authentifizierung mit einem Mock testen:
// Mock für login in AuthContextconst login = async (email, password) => { // Simuliere einen API-Aufruf await new Promise(resolve => setTimeout(resolve, 1000));
// Einfache Validierung if (email === 'user@example.com' && password === 'password') { const mockUser = { email, name: 'Test User' }; setCurrentUser(mockUser); return mockUser; } else { throw new Error('Ungültige Anmeldedaten'); }};Was passiert hier?
Abschnitt betitelt „Was passiert hier?“- AuthContext: Verwaltet den Authentifizierungsstatus in der gesamten Anwendung
- ProtectedRoute: Leitet nicht authentifizierte Benutzer zur Login-Seite weiter
- Login-Komponente: Bietet ein Formular für die Benutzeranmeldung
- Navbar: Zeigt den Authentifizierungsstatus an und ermöglicht das Abmelden
- App.jsx: Integriert den AuthProvider und richtet geschützte Routen ein
Zusammenfassung
Abschnitt betitelt „Zusammenfassung“In diesem Abschnitt haben wir:
- Einen Authentifizierungskontext erstellt, der den Anmeldestatus in der gesamten Anwendung verwaltet
- Eine geschützte Route-Komponente implementiert, die unauthentifizierte Benutzer umleitet
- Eine Login-Seite mit Validierung und Fehlerbehandlung erstellt
- Die Navigationsleiste aktualisiert, um den Anmeldestatus anzuzeigen und Logout-Funktion hinzuzufügen
- Die App.jsx aktualisiert, um den AuthProvider zu integrieren und Routen zu schützen
Unsere Anwendung verfügt jetzt über eine vollständige Authentifizierungslösung, die sicherstellt, dass nur angemeldete Benutzer auf geschützte Seiten zugreifen können.