Wat gaan we doen?

In vorige deel heb je register en login gemaakt. Je krijgt tokens terug, maar workouts zijn nog steeds voor iedereen toegankelijk.

In dit deel gaan we:

  • Middleware maken die tokens checkt
  • Workout routes beveiligen
  • Zorgen dat elke user alleen eigen workouts ziet
  • Frontend code schrijven om tokens te gebruiken

Stap 1: Wat is Middleware?

Denk aan portier bij club:

  1. Je komt bij deur (route)
  2. Portier checkt ID (middleware)
  3. ID klopt? Je mag binnen (controller)
  4. Geen ID of nep ID? Je wordt geweigerd

Middleware zit tussen route en controller in.

Request doorloopt deze stappen:

GET /api/workouts → requireAuth middleware → getAllWorkouts controller

Als middleware faalt, kom je nooit bij controller aan.

Stap 2: Middleware Maken

Maak nieuw bestand in de middleware folder: src/middleware/requireAuth.js

import jwt from 'jsonwebtoken';
import User from '../models/User.js';

export const requireAuth = async (req, res, next) => {
    // Check of er Authorization header is
    const { authorization } = req.headers;

    if (!authorization) {
        return res.status(401).json({ error: 'Je moet ingelogd zijn' });
    }

    // Haal token uit header (format: "Bearer ")
    const token = authorization.split(' ')[1];

    try {
        // Verifieer token
        const { userId } = jwt.verify(token, process.env.JWT_SECRET);

        // Haal user op en zet in request
        req.user = await User.findById(userId).select('_id email');

        // Ga door naar controller
        next();

    } catch (error) {
        res.status(401).json({ error: 'Token is niet geldig' });
    }
};
Wat gebeurt hier stap voor stap:
  • 1. Check of er Authorization header is
  • 2. Haal token uit header
  • 3. jwt.verify() checkt of token geldig is en niet verlopen
  • 4. Haal user data op uit database
  • 5. Zet user in req.user (nu kunnen controllers erbij)
  • 6. next() betekent: ga door naar controller

Waarom "Bearer"?

Authorization header ziet er zo uit:
Authorization: Bearer eyJhbGciOiJIUzI1...

"Bearer" betekent "drager" in Engels. Standaard manier om te zeggen: "ik draag token bij me".

We splitten op spatie en pakken tweede deel (de token).

Stap 3: Middleware Toepassen

Open src/routes/workoutRoutes.js en voeg middleware toe:

import express from 'express';
import { 
    getAllWorkouts, 
    getWorkoutById, 
    createWorkout, 
    updateWorkout, 
    deleteWorkout 
} from '../controllers/workoutController.js';
import { requireAuth } from '../middleware/requireAuth.js';

const router = express.Router();

// Alle routes hieronder checken eerst token
router.use(requireAuth);

router.get('/', getAllWorkouts);
router.get('/:id', getWorkoutById);
router.post('/', createWorkout);
router.patch('/:id', updateWorkout);
router.delete('/:id', deleteWorkout);

export default router;

Wat doet router.use(requireAuth)?

Dit betekent: alle routes hieronder gebruiken eerst deze middleware.

Dus als iemand GET /api/workouts doet:

  1. requireAuth checkt token
  2. Token geldig? Ga door naar getAllWorkouts
  3. Token ongeldig? Stop en stuur 401 error

Zonder geldige token kan niemand meer bij workout routes!

Belangrijk!

router.use(requireAuth) moet VOOR alle andere routes. Anders wordt middleware niet gebruikt.

Stap 4: Testen met Postman

Test 1: Zonder Token (moet falen)

Probeer workouts ophalen zonder token:

  • Method: GET
  • URL: http://localhost:4000/api/workouts
  • Headers: Geen Authorization

Verwacht:

Status: 401 Unauthorized

{
    "error": "Je moet ingelogd zijn"
}

Dit is goed! Het betekent dat routes beveiligd zijn.

Test 2: Met Token (moet werken)

Nu met geldige token. Volg deze stappen:

Stappen:

Stap 1: Log eerst in

  • POST naar /api/auth/login
  • Body: email en password

Stap 2: Kopieer token uit response

Stap 3: Gebruik token

  • GET naar /api/workouts
  • Headers tab in Postman
  • Key: Authorization
  • Value: Bearer jouw_token_hier

Let op!

  • "Bearer" met hoofdletter B
  • Spatie tussen "Bearer" en token
  • Niet: BearerjouwToken
  • Wel: Bearer jouwToken

Als het werkt:

Status: 200 OK
Je ziet workouts (waarschijnlijk van alle gebruikers nog)

Stap 5: Workouts Per Gebruiker

Nu kunnen alleen ingelogde gebruikers workouts zien, maar ze zien ALLE workouts. We willen dat elke gebruiker alleen eigen workouts ziet.

Het probleem:

Piet maakt account en voegt workout toe
Jan maakt account en voegt workout toe

Nu kan Piet ook Jan's workout ophalen, updaten of verwijderen. Dat willen we niet!

Oplossing: We slaan bij elke workout op: van welke gebruiker is deze?

Workout Model Aanpassen

Open src/models/Workout.js en voeg userId veld toe:

import mongoose from 'mongoose';

const workoutSchema = new mongoose.Schema({
    title: {
        type: String,
        required: true
    },
    reps: {
        type: Number,
        required: true
    },
    load: {
        type: Number,
        required: true
    },
    userId: {
        type: mongoose.Schema.Types.ObjectId,
        required: true,
        ref: 'User'
    }
}, {
    timestamps: true
});

const Workout = mongoose.model('Workout', workoutSchema);

export default Workout;
Nieuw veld:
  • userId - Verwijst naar user die workout aanmaakte
  • mongoose.Schema.Types.ObjectId - Type voor MongoDB IDs
  • ref: 'User' - Verwijst naar User model

Let op!

Als je oude workouts in database hebt zonder userId, verwijder die. Anders krijg je errors. Nieuwe workouts hebben automatisch userId.

Stap 6: Controllers Aanpassen

Open src/controllers/workoutController.js en pas ALLE functies aan:

GET Alle Workouts

export const getAllWorkouts = async (req, res) => {
    try {
        const workouts = await Workout.find({ userId: req.user._id })
            .sort({ createdAt: -1 });
        res.status(200).json(workouts);
    } catch (error) {
        res.status(500).json({ error: 'Server error' });
    }
};
Wat is nieuw?
  • { userId: req.user._id } - Alleen workouts van deze user
  • req.user._id komt uit middleware!

POST Nieuwe Workout

export const createWorkout = async (req, res) => {
    const { title, reps, load } = req.body;

    try {
        const workout = await Workout.create({ 
            title, 
            reps, 
            load,
            userId: req.user._id
        });
        res.status(201).json(workout);
    } catch (error) {
        res.status(400).json({ error: error.message });
    }
};
Wat is nieuw?
  • userId: req.user._id - Sla op welke user workout maakte

UPDATE Workout

export const updateWorkout = async (req, res) => {
    const { id } = req.params;

    if (!mongoose.isValidObjectId(id)) {
        return res.status(400).json({ error: 'Geen geldige workout ID' });
    }

    try {
        const workout = await Workout.findOneAndUpdate(
            { _id: id, userId: req.user._id },
            { ...req.body },
            { new: true }
        );

        if (!workout) {
            return res.status(404).json({ error: 'Workout niet gevonden' });
        }

        res.status(200).json(workout);
    } catch (error) {
        res.status(500).json({ error: 'Server error' });
    }
};

DELETE Workout

export const deleteWorkout = async (req, res) => {
    const { id } = req.params;

    if (!mongoose.isValidObjectId(id)) {
        return res.status(400).json({ error: 'Geen geldige workout ID' });
    }

    try {
        const workout = await Workout.findOneAndDelete({ 
            _id: id, 
            userId: req.user._id
        });

        if (!workout) {
            return res.status(404).json({ error: 'Workout niet gevonden' });
        }

        res.status(200).json(workout);
    } catch (error) {
        res.status(500).json({ error: 'Server error' });
    }
};

Waarom { _id: id, userId: req.user._id }?

Concreet voorbeeld:

Piet (userId: 111) probeert Jan's workout (id: 456, userId: 222) te verwijderen.

findOneAndDelete({ _id: 456, userId: 111 })

Mongoose zoekt workout met:

  • ID = 456 ✓ (bestaat)
  • EN userId = 111 ✗ (workout is van Jan, userId 222)

Workout niet gevonden! Piet kan Jan's workout niet verwijderen.

Dit is dubbele check: juiste ID EN van dezelfde gebruiker.

Stap 7: Testen User-Specific

Test met 2 Accounts

Account 1 (Piet):

  1. Register met piet@test.nl
  2. Login en kopieer token
  3. POST workout met deze token
  4. GET workouts → zie alleen Piet's workout

Account 2 (Jan):

  1. Register met jan@test.nl
  2. Login en kopieer token
  3. POST workout met Jan's token
  4. GET workouts → zie alleen Jan's workout

Test beveiliging:

  1. Probeer met Jan's token Piet's workout verwijderen
  2. Result: 404 Workout niet gevonden

Het werkt als:

  • Elke user ziet alleen eigen workouts
  • Users kunnen elkaars workouts niet updaten of verwijderen
  • Je krijgt 404 als je workout probeert bereiken die niet van jou is

Stap 8: Frontend Implementatie

Nu moet frontend tokens kunnen versturen en opslaan.

Wat is localStorage?

localStorage is plek in browser waar je data kan opslaan. Data blijft staan, zelfs als je browser sluit.

Perfect voor tokens! Als je inlogt, slaan we token op. Bij elk nieuw request halen we token weer op.

Login Component

import { useState } from 'react';

function Login() {
    const [email, setEmail] = useState('');
    const [password, setPassword] = useState('');
    const [error, setError] = useState('');

    const handleLogin = async (e) => {
        e.preventDefault();

        try {
            const response = await fetch('http://localhost:4000/api/auth/login', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ email, password })
            });

            const data = await response.json();

            if (response.ok) {
                // Sla token op
                localStorage.setItem('token', data.token);
                console.log('Ingelogd!');
                // Redirect of refresh workouts
            } else {
                setError(data.error);
            }
        } catch (error) {
            console.error('Error:', error);
        }
    };

    return (
        <form onSubmit={handleLogin}>
            <input
                type="email"
                placeholder="Email"
                value={email}
                onChange={(e) => setEmail(e.target.value)}
            />
            <input
                type="password"
                placeholder="Wachtwoord"
                value={password}
                onChange={(e) => setPassword(e.target.value)}
            />
            <button>Inloggen</button>
            {error && <p>{error}</p>}
        </form>
    );
}
Export default Login

Workouts Ophalen met Token

const fetchWorkouts = async () => {
    const token = localStorage.getItem('token');

    if (!token) {
        console.log('Niet ingelogd');
        return;
    }

    try {
        const response = await fetch('http://localhost:4000/api/workouts', {
            headers: {
                'Authorization': `Bearer ${token}`
            }
        });

        const data = await response.json();

        if (response.ok) {
            setWorkouts(data);
        } else {
            console.error(data.error);
        }
    } catch (error) {
        console.error('Error:', error);
    }
};
Wat gebeurt hier?
  • Haal token op uit localStorage
  • Check of token bestaat
  • Stuur token mee in Authorization header
  • Backend checkt token en geeft alleen jouw workouts terug

Logout Functie

const handleLogout = () => {
    localStorage.removeItem('token');
    console.log('Uitgelogd');
    // Redirect naar login pagina of clear workouts
};

Veelvoorkomende Fouten

❌ "Je moet ingelogd zijn" bij elke request

Check in Postman:

  • Heb je Authorization header toegevoegd?
  • Format: Bearer <token> (met hoofdletter en spatie!)
  • Is token niet verlopen? (7 dagen geldig)

❌ "Token is niet geldig"

Mogelijke oorzaken:

  • Token is verlopen → log opnieuw in
  • Token is corrupt → kopieer hele token zonder extra spaties
  • JWT_SECRET is veranderd → tokens ongeldig geworden

❌ Zie workouts van andere users

Check of:

  • Je { userId: req.user._id } gebruikt bij find()
  • Middleware correct werkt (req.user bestaat)
  • Oude workouts zonder userId verwijderd zijn

❌ "req.user is undefined"

Check:

  • Middleware wordt niet aangeroepen → check router.use(requireAuth)
  • Staat router.use(requireAuth) VOOR routes?
  • Import je requireAuth correct?

❌ Frontend: Kan niet inloggen

Check:

  • Is backend aan? Check terminal
  • CORS ingesteld? Check server.js
  • Juiste URL? (http://localhost:4000/api/auth/login)
  • Check console voor errors (F12)

❌ Token blijft niet opgeslagen

Check:

  • localStorage.setItem() wordt aangeroepen?
  • Incognito mode kan localStorage blokkeren
  • Check browser console: localStorage.getItem('token')

Samenvatting

Je hebt nu volledig beveiligd systeem!

Wat je hebt gemaakt:

  • Middleware die tokens checkt
  • Beveiligde workout routes
  • User-specific workouts (elke user ziet alleen eigen data)
  • Frontend login/logout met token opslag
  • Authorization headers bij requests

De hele flow:

  1. User logt in → krijgt token
  2. Token wordt opgeslagen in localStorage
  3. Bij elk request: token wordt meegestuurd
  4. Middleware checkt token
  5. Controller krijgt user info uit token
  6. Database query filtert op userId
  7. User ziet alleen eigen data

Je hebt nu een complete MERN Stack applicatie gebouwd!

Met authentication, authorization, CRUD operaties en een beveiligde API.