Twee stukken
Beveiliging in de frontend bestaat uit twee dingen:
1. Token meesturen
De backend wil bij elke beveiligde route een Authorization-header zien met je JWT. Anders
krijg je een 401.
2. Pagina's afschermen
Niet-ingelogde gebruikers moeten niet bij de workouts-pagina kunnen. Die stuur je terug naar
/login.
Frontend-beveiliging is niet écht veilig
Een handige gebruiker kan de browser-checks omzeilen. De échte beveiliging zit in de backend (de
requireAuth middleware). Frontend-protection is puur voor de UX.
Bearer header in fetches
Standaard ziet een fetch er zo uit:
await fetch('http://localhost:4000/api/workouts');
Voor een beveiligde route voeg je headers toe:
await fetch('http://localhost:4000/api/workouts', {
headers: {
'Authorization': `Bearer ${token}`
}
});
- Het is een afspraak:
Bearer <token>betekent "ik ben de drager van deze token". - De backend pakt alleen het stukje na
Beareren valideert dat als JWT. - Mis je het woordje
Bearer? Dan krijg je 401.
Token in elke CRUD-call
Pak token uit je AuthContext en plak hem in elke fetch:
// frontend/src/components/WorkoutList.jsx
import { useAuth } from '../context/AuthContext';
function WorkoutList() {
const { token } = useAuth();
const [workouts, setWorkouts] = useState([]);
useEffect(() => {
if (!token) return; // nog niet ingelogd
const fetchWorkouts = async () => {
const response = await fetch('http://localhost:4000/api/workouts', {
headers: { 'Authorization': `Bearer ${token}` }
});
const data = await response.json();
setWorkouts(data);
};
fetchWorkouts();
}, [token]);
// ...
}
Voor POST, PATCH en DELETE: dezelfde header naast de bestaande Content-Type:
await fetch('http://localhost:4000/api/workouts', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify(newWorkout)
});
useEffectdependency is[token]— als de token verandert (bijv. na login), draait de fetch opnieuw.if (!token) return;— voorkom dat je een fetch doet zonder token (geeft sowieso een 401).Content-TypeenAuthorizationstaan beide in dezelfdeheaders-object.
ProtectedRoute component
Een wrapper-component die kijkt of er een ingelogde user is. Zo ja: render de pagina. Zo nee: redirect naar login.
// frontend/src/components/ProtectedRoute.jsx
import { Navigate } from 'react-router-dom';
import { useAuth } from '../context/AuthContext';
function ProtectedRoute({ children }) {
const { user } = useAuth();
if (!user) {
return <Navigate to="/login" replace />;
}
return children;
}
export default ProtectedRoute;
children— wat je tussen<ProtectedRoute>...</ProtectedRoute>zet.<Navigate to="/login" replace />— React Router stuurt de gebruiker direct door.replacezorgt dat de huidige pagina niet in de geschiedenis komt.- Geen user? Geen content. Wel user? Render gewoon door.
Router opzet
Wikkel je beveiligde routes in ProtectedRoute. Login en register laat je publiek:
// frontend/src/App.jsx
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import Home from './pages/Home';
import Login from './pages/Login';
import Register from './pages/Register';
import ProtectedRoute from './components/ProtectedRoute';
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/login" element={<Login />} />
<Route path="/register" element={<Register />} />
<Route
path="/"
element={
<ProtectedRoute>
<Home />
</ProtectedRoute>
}
/>
</Routes>
</BrowserRouter>
);
}
export default App;
Patroon
Elke pagina die login vereist krijg je in een <ProtectedRoute>. Login en register
laat je publiek, anders kan niemand erin komen.
Testen met twee accounts
De ultieme test
- Maak account A aan, voeg een paar workouts toe.
- Logout. Maak account B aan, voeg andere workouts toe.
- Logout. Login weer als A — je ziet alleen jouw workouts, niet die van B.
- Probeer de URL
/te openen in een incognito-venster zonder login → je belandt op/login.
Werkt alle vier? Dan staat je beveiliging.
Veelgemaakte fouten
1. Authorization header vergeten in één van de calls
POST werkt, GET werkt, maar DELETE geeft 401. Loop alle vier (GET/POST/PATCH/DELETE) na — overal moet de header staan.
2. Bearer woord vergeten
'Authorization': token i.p.v. `Bearer ${token}`. Backend kan de token niet
parsen.
3. useEffect dependency op [] in plaats van [token]
Bij login draait de fetch niet opnieuw, want React denkt dat er niets veranderd is. Zet
token in de dependency array.
4. ProtectedRoute checkt token ipv user
Bij refresh staat de token meteen in localStorage maar duurt het een tick voor user terugkomt. Kies
één bron en blijf daarbij. user is meestal het meest betrouwbaar.
5. Frontend-only beveiliging
Een aanvaller skipt gewoon de browser. Vergeet nooit requireAuth in de backend.
Klaar!
Je hebt het hele frontend-pad doorlopen.
Je weet nu hoe je een React-frontend bouwt die met je eigen backend praat — van een simpele lijst tot een volledig beveiligde multi-user app. Tijd om je eigen project op te poetsen.
Bekijk alle onderwerpen van de cheat sheet