Ein Auth-Context und Login-Formular
Ein Auth-Context und Login-Formular
~16 Min. Lesezeit Zuletzt aktualisiert am 8. August 2026
Der Token aus Kapitel 49 muss ZENTRAL verwaltet werden, ZUGÄNGLICH für JEDE Komponente, die wissen muss, OB und ALS WER der Nutzer eingeloggt ist – GENAU der Anwendungsfall aus Kapitel 73 für React Context.
Den AuthContext erstellen
import { createContext, useContext, useState, type ReactNode } from 'react';
interface AuthContextValue {
token: string | null;
login: (token: string) => void;
logout: () => void;
}
const AuthContext = createContext<AuthContextValue | null>(null);
export function AuthProvider({ children }: { children: ReactNode }) {
const [token, setToken] = useState<string | null>(
() => localStorage.getItem('token'),
);
function login(newToken: string) {
localStorage.setItem('token', newToken);
setToken(newToken);
}
function logout() {
localStorage.removeItem('token');
setToken(null);
}
return (
<AuthContext.Provider value={{ token, login, logout }}>
{children}
</AuthContext.Provider>
);
}
export function useAuth() {
const context = useContext(AuthContext);
if (!context) {
throw new Error('useAuth muss innerhalb von AuthProvider verwendet werden');
}
return context;
}Achtung: localStorage ist die EINFACHSTE Lösung, aber ANFÄLLIG für XSS (Kapitel 56 kündigte diese Abwägung an) – für UNSER Lernprojekt AUSREICHEND, für ein PRODUKTIONSSYSTEM würde ein HTTP-only-Cookie (ausgestellt vom Backend) das SICHERERE Muster sein, dann ALLERDINGS mit zusätzlichem CSRF-Schutz.
Den Provider einbinden
// main.tsx - AuthProvider UMSCHLIESST App, INNERHALB von QueryClientProvider
<QueryClientProvider client={queryClient}>
<AuthProvider>
<App />
</AuthProvider>
</QueryClientProvider>Das Login-Formular
import { useState, type FormEvent } from 'react';
import { useMutation } from '@tanstack/react-query';
import { apiClient } from '../api/client';
import { useAuth } from '../context/AuthContext';
function LoginPage() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const { login } = useAuth();
const loginMutation = useMutation({
mutationFn: async () => {
const response = await apiClient.post<{ token: string }>('/login', {
email,
password,
});
return response.data;
},
onSuccess: (data) => {
login(data.token);
},
});
function handleSubmit(event: FormEvent) {
event.preventDefault();
loginMutation.mutate();
}
return (
<form onSubmit={handleSubmit}>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
<button type="submit" disabled={loginMutation.isPending}>
Anmelden
</button>
{loginMutation.isError && <p>Anmeldung fehlgeschlagen</p>}
</form>
);
}
export default LoginPage;useMutation statt useQuery – GENAU wie in der Symfony-Schulung Formulare zwischen ANZEIGEN (GET) und ÄNDERN (POST) unterscheiden, unterscheidet TanStack Query zwischen LESENDEN (useQuery) und SCHREIBENDEN (useMutation) Operationen.
Tipp: apiClient.post('/login', ...) nutzt den Content-Type: application/ld+json-Header aus Kapitel 74 – der Login-Endpunkt (Kapitel 49) verlangt aber SCHLICHTES JSON. Praktisch funktioniert das MEISTENS trotzdem (Symfony ist TOLERANT), sauberer wäre ein SEPARATER Content-Type-Override NUR für diesen Aufruf.