Authentication in Node.js is één van de belangrijkste onderdelen van bijna elke applicatie. Of je nu een REST API bouwt, een SaaS-dashboard of een webshop, gebruikers moeten kunnen inloggen, en dat moet veilig én gebruiksvriendelijk gebeuren.
Twee mechanismen domineren het landschap: JSON Web Tokens (JWT) en server-side sessions. Beide werken, beide hebben hun plek, maar ze lossen het probleem fundamenteel anders op. In deze gids leer je precies hoe ze werken, wanneer je welke kiest, en hoe je ze veilig implementeert in Node.js.
We gaan uit van kennis over HTTP servers en routing en middleware patterns. Nieuw bij Node.js? Start dan eerst met wat is Node.js en hoe werkt de event loop.
Wat is authentication eigenlijk?
Authentication beantwoordt de vraag: "Wie ben jij?". Dat is iets anders dan authorization, dat bepaalt wat je mag doen.
Het proces bestaat meestal uit twee fases:
- Inloggen: de gebruiker stuurt credentials (bijvoorbeeld e-mail en wachtwoord) naar de server. De server verifieert deze en geeft een bewijs van identiteit terug.
- Opvolgende requests: bij elke volgende request stuurt de client dat bewijs mee, zodat de server weet wie er aan de andere kant zit.
HTTP is van nature stateless. Elke request staat op zichzelf. De manier waarop je dat "bewijs van identiteit" bijhoudt, is precies waar JWT en sessions van elkaar verschillen.
Sessions: state op de server
Bij session-based authentication slaat de server de sessie-informatie centraal op. De client krijgt alleen een onvoorspelbaar session-ID via een cookie.
Hoe het werkt
- Gebruiker logt in met credentials.
- Server valideert ze en maakt een session-record aan (bijvoorbeeld in Redis of MySQL).
- Server zet een cookie
session_id=abc123...in de response. - Bij elke volgende request stuurt de browser automatisch die cookie mee.
- De server zoekt
abc123op in de session store en weet wie je bent.
Voorbeeld met Express en express-session
import express from 'express';
import session from 'express-session';
import RedisStore from 'connect-redis';
import { createClient } from 'redis';
const redisClient = createClient();
await redisClient.connect();
const app = express();
app.use(express.json());
app.use(session({
store: new RedisStore({ client: redisClient }),
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
cookie: {
httpOnly: true,
secure: true,
sameSite: 'lax',
maxAge: 1000 * 60 * 60 * 24,
},
}));
app.post('/login', async (req, res) => {
const user = await authenticate(req.body.email, req.body.password);
if (!user) return res.status(401).json({ error: 'Invalid credentials' });
req.session.userId = user.id;
res.json({ ok: true });
});
app.get('/me', (req, res) => {
if (!req.session.userId) return res.status(401).end();
res.json({ userId: req.session.userId });
});
Voordelen van sessions
- Directe invalidatie: verwijder het session-record en de gebruiker is uitgelogd.
- Kleine payload: alleen een ID reist over het netwerk.
- Minder risico bij data-lekkage: gevoelige info staat op de server.
- Simpel mentaal model: past bij klassieke webapplicaties.
Nadelen van sessions
- State op de server: je hebt een shared store nodig zodra je meerdere instances draait.
- Minder geschikt voor mobile en third-party API's: cookies werken niet altijd soepel buiten een browser.
- CSRF-risico: cookies worden automatisch meegestuurd, dus CSRF-bescherming is noodzakelijk.
JWT: stateless tokens
Een JSON Web Token is een zelfbeschrijvend, cryptografisch ondertekend token. Alle benodigde info zit erin, dus de server hoeft niets op te slaan.
Een JWT bestaat uit drie delen, gescheiden door punten: header.payload.signature. De payload bevat bijvoorbeeld de user ID, rollen en een vervaldatum. De signature zorgt dat de payload niet gemanipuleerd kan worden zonder de secret.
Hoe het werkt
- Gebruiker logt in met credentials.
- Server valideert en genereert een JWT met
jsonwebtoken. - Client slaat het token op (bij voorkeur in een httpOnly cookie).
- Bij elke request stuurt de client het token mee, meestal in de
Authorization: Bearer ...header. - Server verifieert de signature en leest de payload.
Voorbeeld met Express en jsonwebtoken
import jwt from 'jsonwebtoken';
const JWT_SECRET = process.env.JWT_SECRET;
app.post('/login', async (req, res) => {
const user = await authenticate(req.body.email, req.body.password);
if (!user) return res.status(401).json({ error: 'Invalid credentials' });
const token = jwt.sign(
{ sub: user.id, role: user.role },
JWT_SECRET,
{ expiresIn: '15m' }
);
res.json({ token });
});
function authMiddleware(req, res, next) {
const header = req.headers.authorization;
if (!header?.startsWith('Bearer ')) return res.status(401).end();
try {
const payload = jwt.verify(header.slice(7), JWT_SECRET);
req.user = { id: payload.sub, role: payload.role };
next();
} catch {
res.status(401).json({ error: 'Invalid token' });
}
}
app.get('/me', authMiddleware, (req, res) => {
res.json(req.user);
});
Voordelen van JWT
- Stateless: geen database-lookup nodig bij elke request.
- Schaalbaar: perfect voor microservices en load-balanced setups.
- Cross-platform: werkt even goed voor mobile apps, SPA's en API-to-API-communicatie.
- Zelfbeschrijvend: rollen en claims zitten direct in het token.
Nadelen van JWT
- Moeilijk te revokeren: tot expiratie blijft een token geldig, tenzij je een blocklist bijhoudt.
- Grotere payload: elk token bevat alle claims en reist mee bij elke request.
- XSS-gevoelig bij localStorage: veel tutorials laten dit zien, maar het is risicovol.
- Complexere refresh-flow: korte access tokens vragen om refresh tokens.
JWT vs sessions: wanneer kies je wat?
Er is geen absolute winnaar. De keuze hangt af van je architectuur.
Kies sessions als:
- Je een klassieke webapp bouwt met één backend en één frontend.
- Je snel sessies moet kunnen invalideren (bij wachtwoordwijziging, ban, etc.).
- Je team al vertrouwd is met cookie-based flows.
- Je Redis of een andere snelle store al draait.
Kies JWT als:
- Je een publieke API of mobile-first product bouwt.
- Je meerdere services hebt die allemaal dezelfde identiteit moeten begrijpen.
- Je stateless horizontaal wilt schalen zonder sticky sessions.
- Je SSO of OAuth2 integreert, die protocollen werken van nature met tokens.
Een hybride aanpak komt ook veel voor: een JWT voor de access token, plus een refresh token in een httpOnly cookie. Zo combineer je stateless verificatie met de mogelijkheid om snel in te trekken.
Wachtwoorden opslaan: de basis
Wat je ook kiest qua token-mechanisme: sla wachtwoorden nooit als plain text op. Gebruik een moderne hash-functie zoals bcrypt of Argon2.
import bcrypt from 'bcrypt';
const hash = await bcrypt.hash(plainPassword, 12);
const valid = await bcrypt.compare(plainPassword, hash);
De cost factor (hier 12) bepaalt hoe zwaar de berekening is. Hoger is veiliger, maar trager. Valideer inputs verder met Zod voordat je ze doorstuurt naar je auth-logica, dat voorkomt onverwachte types of injection-pogingen.
Refresh tokens: de praktijk
Korte access tokens (bijvoorbeeld 15 minuten) zijn veiliger, maar zouden gebruikers constant uitloggen. Daarom combineer je ze met een refresh token dat langer geldig is (bijvoorbeeld 7 dagen).
Flow:
- Gebruiker logt in → krijgt access token (kort) én refresh token (lang).
- Access token zit in memory of Authorization header.
- Refresh token zit in een httpOnly, Secure cookie op een beperkte path (
/auth/refresh). - Als het access token verloopt, roept de client
/auth/refreshaan. - Server verifieert het refresh token, roteert het (nieuw token uitgeven, oude invalideren) en geeft een nieuw access token.
Sla refresh tokens op in een database of Redis, zodat je ze kunt revoken. Zie ook onze gids over werken met databases in Node.js voor een solide opslaglaag.
Security best practices
Los van de keuze JWT of sessions gelden deze regels:
- Gebruik altijd HTTPS. Tokens en cookies zijn zonder TLS triviaal af te luisteren.
- Zet
httpOnly,SecureenSameSiteop cookies. Voorkomt XSS-diefstal en CSRF. - Rate-limit je login-endpoint. Brute force-aanvallen zijn vervelend goedkoop.
- Voeg CSRF-bescherming toe bij cookie-based flows, bijvoorbeeld met double-submit tokens.
- Log security-events zoals mislukte logins, refresh-token-hergebruik en wachtwoordwijzigingen.
- Valideer en sanitize alle input. Koppel dit aan je REST API design.
- Kies sterke secrets (minstens 32 bytes random) en bewaar ze buiten je repo, bijvoorbeeld via environment variables.
- Rotate secrets periodiek. Plan dit vóór je het nodig hebt.
Voor een dieper duik in webapp-kwetsbaarheden raden we de OWASP Top 10 aan. Voor JWT-specifieke details is RFC 7519 de formele referentie, en jwt.io helpt tokens te decoden en algoritmes te vergelijken.
Veelvoorkomende valkuilen
alg: noneaccepteren: zorg dat je JWT-library expliciet een algoritme afdwingt (bijvoorbeeldHS256ofRS256).- JWT's in localStorage: XSS betekent directe tokendiefstal. Gebruik httpOnly cookies.
- Geen expiratie zetten: een token zonder
exp-claim blijft eeuwig geldig. - Sessions zonder rotatie: rouleer het session-ID bij login en privilege-escalatie om session fixation te voorkomen.
- Secrets in de repo: scan je git-history en verwijder ze structureel met tools als
git-secrets. - Async-fouten negeren: wrap je handlers correct, zie onze gids over async programming.
Veelgestelde vragen
Wat is het verschil tussen JWT en sessions?
Sessions slaan de sessiestate op de server op en sturen alleen een session-ID naar de client via een cookie. JWT is een zelfstandig token dat alle gebruikersdata cryptografisch ondertekend meestuurt, zodat de server geen state hoeft bij te houden.
Wanneer kies je JWT en wanneer sessions?
Kies sessions voor traditionele webapps waar je één backend en één frontend hebt. JWT is geschikter voor API's, mobile apps en microservices waar stateless authenticatie en schaalbaarheid belangrijk zijn.
Waar sla je een JWT veilig op in de browser?
Sla JWT's bij voorkeur op in een httpOnly, Secure en SameSite cookie. localStorage is kwetsbaar voor XSS-aanvallen, omdat JavaScript er toegang toe heeft.
Hoe log je een gebruiker uit bij JWT?
Omdat JWT stateless is, kun je niet zomaar een token invalideren. Werk met korte levensduur, refresh tokens en eventueel een blocklist in Redis voor ingetrokken tokens.
Is bcrypt nog steeds de beste keuze voor wachtwoorden?
Bcrypt is een prima keuze en breed ondersteund. Argon2 geldt momenteel als de modernere standaard en wint aan populariteit in Node.js-projecten.
Tot slot
JWT en sessions zijn geen concurrenten, het zijn gereedschappen voor verschillende problemen. Sessions blinken uit in klassieke webapps met directe controle. JWT wint bij stateless, schaalbare en cross-platform scenario's.
Begin met de eenvoudigste oplossing die bij je architectuur past, bouw security vanaf dag één in, en upgrade naar een hybride aanpak zodra je product daarom vraagt. Zo voorkom je onnodige complexiteit én kwetsbaarheden.