Wat is Authentication?

Nu kan iedereen alle workouts zien en aanpassen. Niet veilig. Met authentication kan elke gebruiker alleen hun eigen workouts beheren.

Hoe werkt het?

1. Registreren: Maak account met email en wachtwoord
2. Inloggen: Backend geeft token (toegangspas)
3. Token meesturen: Bij elk request stuur je token mee
4. Backend checkt: Is token geldig? Zo ja, je mag door

Twee belangrijke packages

1. JWT (JSON Web Token)

Dit is lange string die bewijst dat je bent ingelogd:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiI2NzRhM2Y4ZDk4...

Je hoeft niet te begrijpen HOE dit werkt. Je moet alleen weten:

  • Backend maakt token na inloggen
  • Frontend stuurt token mee bij elk request
  • Token bevat je user ID
  • Token is 7 dagen geldig

2. bcrypt (Password Hashing)

bcrypt versleutelt wachtwoorden. Iemand hackt je database? Dan zien ze niet "Piet123" maar dit:

$2b$10$KmX8Yv3Lq9Np2Rs4Tw5Ux.HpZcJ6Tv8GcL4mN9oP...

Zo zijn wachtwoorden veilig, zelfs als database gehackt wordt.

Stap 1: Packages Installeren

Installeer in je backend:

npm install jsonwebtoken bcrypt
Wat installeren we?
  • jsonwebtoken - Voor maken en verifiëren JWT tokens
  • bcrypt - Voor hashen wachtwoorden

Stap 2: User Model Maken

Maak nieuw bestand: src/models/User.js

import mongoose from 'mongoose';
import bcrypt from 'bcrypt';

const userSchema = new mongoose.Schema({
    email: {
        type: String,
        required: true,
        unique: true,
        lowercase: true
    },
    password: {
        type: String,
        required: true,
        minlength: 6
    }
}, {
    timestamps: true
});

// Dit gebeurt automatisch VOOR het opslaan
userSchema.pre('save', async function() {
    if (!this.isModified('password')) {
        return; 
    }
    
    // Hash het wachtwoord
    this.password = await bcrypt.hash(this.password, 10);
});

// Functie om wachtwoorden te vergelijken
userSchema.methods.comparePassword = async function(candidatePassword) {
    return await bcrypt.compare(candidatePassword, this.password);
};

const User = mongoose.model('User', userSchema);

export default User;
Belangrijke delen:
  • unique: true - Elk email maar 1x gebruiken
  • lowercase: true - Zet email naar kleine letters
  • minlength: 6 - Wachtwoord minimaal 6 karakters

Wat doet pre('save')?

Dit draait automatisch VOOR user wordt opgeslagen.

Als je doet: User.create({ email, password })

Dan gebeurt:

  1. Mongoose runt eerst pre('save')
  2. Wachtwoord wordt gehashed
  3. Dan pas opslaan in database

Jij hoeft dit niet handmatig te doen - Mongoose doet automatisch!

Wat doet comparePassword?

Custom functie die je later gebruikt:

const isMatch = await user.comparePassword('Piet123');

Vergelijkt normaal wachtwoord met gehashte wachtwoord in database.

Stap 3: JWT Secret Toevoegen

Open .env en voeg toe:

JWT_SECRET=mijn_super_geheime_string_12345

Wat is JWT_SECRET?

Geheime sleutel om tokens te maken. Gebruik lange willekeurige string. Mag NOOIT gedeeld worden.

Zelf een veilige secret genereren

Voer dit uit in je terminal om een willekeurige secret te maken:

node -e "console.log(require('crypto').randomBytes(64).toString('hex'))"

Kopieer de output en plak die als waarde in je .env:

JWT_SECRET=a3f8c2e1b4d6f9a2c5e8b1d4f7a0c3e6...

Stap 4: Auth Controller Maken

Maak nieuw bestand: src/controllers/authController.js

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

// Helper functie om token te maken
const createToken = (userId) => {
    return jwt.sign({ userId }, process.env.JWT_SECRET, {
        expiresIn: '7d'
    });
};

// REGISTER - nieuwe gebruiker aanmaken
export const register = async (req, res) => {
    const { email, password } = req.body;

    try {
        // Check of email al bestaat
        const exists = await User.findOne({ email });
        if (exists) {
            return res.status(400).json({ error: 'Email is al in gebruik' });
        }

        // Check of velden ingevuld zijn
        if (!email || !password) {
            return res.status(400).json({ error: 'Vul alle velden in' });
        }

        // Check wachtwoord lengte
        if (password.length < 6) {
            return res.status(400).json({ 
                error: 'Wachtwoord moet minimaal 6 karakters zijn' 
            });
        }

        // Maak nieuwe gebruiker
        const user = await User.create({ email, password });

        // Maak token
        const token = createToken(user._id);

        // Stuur token terug
        res.status(201).json({ email: user.email, token });

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

// LOGIN - bestaande gebruiker inloggen
export const login = async (req, res) => {
    const { email, password } = req.body;

    try {
        // Check of velden ingevuld zijn
        if (!email || !password) {
            return res.status(400).json({ error: 'Vul alle velden in' });
        }

        // Zoek gebruiker
        const user = await User.findOne({ email });
        if (!user) {
            return res.status(400).json({ 
                error: 'Email of wachtwoord incorrect' 
            });
        }

        // Check wachtwoord
        const isMatch = await user.comparePassword(password);
        if (!isMatch) {
            return res.status(400).json({ 
                error: 'Email of wachtwoord incorrect' 
            });
        }

        // Maak token
        const token = createToken(user._id);

        // Stuur token terug
        res.status(200).json({ email: user.email, token });

    } catch (error) {
        res.status(500).json({ error: 'Server error' });
    }
};
Wat doet deze code?
  • createToken() - Maakt JWT token met user ID
  • expiresIn: '7d' - Token 7 dagen geldig
  • Register: Checkt of email uniek is, maakt user, geeft token
  • Login: Zoekt user, checkt wachtwoord, geeft token

Waarom niet "email bestaat niet" of "wachtwoord fout"?

We zeggen altijd "Email of wachtwoord incorrect". Dit is voor security - hackers kunnen anders raden welke emails bestaan.

Stap 5: Auth Routes Maken

Maak nieuw bestand: src/routes/authRoutes.js

import express from 'express';
import { register, login } from '../controllers/authController.js';

const router = express.Router();

// POST /api/auth/register
router.post('/register', register);

// POST /api/auth/login
router.post('/login', login);

export default router;

Voeg 2 regels voor de route toe aan server.js:

import express from 'express';
import cors from 'cors';
import mongoose from 'mongoose';
import workoutRoutes from './src/routes/workoutRoutes.js';
import authRoutes from './src/routes/authRoutes.js'; // Voeg deze regel toe

const app = express();

app.use(cors({ origin: 'http://localhost:5173' }));
app.use(express.json());

// Routes
app.use('/api/workouts', workoutRoutes);
app.use('/api/auth', authRoutes); // Voeg deze regel toe

// Database verbinden en server starten
mongoose.connect(process.env.MONGO_URI)
    .then(() => {
        app.listen(process.env.PORT, () => {
            console.log('Database verbonden & server draait');
        });
    })
    .catch((err) => console.error(err));

Stap 6: Testen met Postman

Test 1: Registreren

  • Method: POST
  • URL: http://localhost:4000/api/auth/register
  • Body (raw JSON):
{
    "email": "test@test.nl",
    "password": "test123"
}

Verwacht resultaat:

Status: 201 Created

{
    "email": "test@test.nl",
    "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}

Test 2: Inloggen

  • Method: POST
  • URL: http://localhost:4000/api/auth/login
  • Body (raw JSON):
{
    "email": "test@test.nl",
    "password": "test123"
}

Verwacht resultaat:

Status: 200 OK
Zelfde response als register (email + token)

Test 3: Foute Credentials

Probeer inloggen met verkeerd wachtwoord:

{
    "email": "test@test.nl",
    "password": "fout_wachtwoord"
}

Verwacht resultaat:

Status: 400 Bad Request

{
    "error": "Email of wachtwoord incorrect"
}

Veelvoorkomende Fouten

❌ "Email is al in gebruik"

Probleem: Account met dit email bestaat al

Oplossing: Gebruik ander email of log in met bestaand account

❌ Wachtwoord niet gehashed

Probleem: Wachtwoord staat als platte tekst in database

Check:

  • userSchema.pre('save') correct in User.js?
  • Check MongoDB Atlas - wachtwoord moet lange onleesbare string zijn
  • Verwijder verkeerde users en maak opnieuw aan

❌ "Email of wachtwoord incorrect" bij login

Check:

  • Email adres bestaat? (registreer eerst!)
  • Wachtwoord correct ingetypt?
  • comparePassword functie werkt in User model?

❌ Geen token in response

Check:

  • JWT_SECRET in .env?
  • createToken() functie returnt token?
  • res.json({ email, token }) en niet alleen email?

Samenvatting

Je hebt nu basis authentication werkend!

Wat je hebt gemaakt:

  • User model met password hashing
  • Register endpoint (POST /api/auth/register)
  • Login endpoint (POST /api/auth/login)
  • JWT tokens die je kan gebruiken

Test alles goed!

Zorg dat je meerdere accounts kan maken en kan inloggen. Je krijgt elke keer token terug. Deze tokens ga je in volgende deel gebruiken om routes te beveiligen.

Volgende Stap

Je hebt accounts en tokens. Tijd om routes te beveiligen.

Protected Routes →

Beveilig je workout routes met middleware