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}`
  }
});
Wat is "Bearer"?
  • Het is een afspraak: Bearer <token> betekent "ik ben de drager van deze token".
  • De backend pakt alleen het stukje na Bearer en 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)
});
Belangrijke details
  • useEffect dependency 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-Type en Authorization staan beide in dezelfde headers-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;
Wat doet dit?
  • children — wat je tussen <ProtectedRoute>...</ProtectedRoute> zet.
  • <Navigate to="/login" replace /> — React Router stuurt de gebruiker direct door. replace zorgt 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

  1. Maak account A aan, voeg een paar workouts toe.
  2. Logout. Maak account B aan, voeg andere workouts toe.
  3. Logout. Login weer als A — je ziet alleen jouw workouts, niet die van B.
  4. 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.

Terug naar MERN Overzicht →

Bekijk alle onderwerpen van de cheat sheet