Als je een API bouwt, stuit je vroeg of laat op één vraag: nadat een gebruiker is ingelogd, hoe herken ik diegene bij elke volgende request? Het antwoord op wat is JWT ligt precies hier. Een JWT (JSON Web Token) is als een zelfstandige identiteitskaart die de server aanmaakt, ondertekent en aan de client geeft. De client stuurt die kaart bij elke request terug, en de server kan de handtekening controleren en zeggen "ja, dit is echt een token dat ik heb uitgegeven" — zonder ooit de database te raadplegen.
Wat is JWT en waarom gebruik je het?
Bij klassiek sessiebeheer bewaart de server voor elke ingelogde gebruiker een session-record en geeft de client een session-id. Dat werkt, maar de server moet een toestand onthouden. Heb je meerdere servers, dan moeten ze allemaal dezelfde sessieopslag delen.
JWT draait dit om. Het token bevat zelf de informatie over de gebruiker (wie diegene is, de rol, een vervaltijd) en is ondertekend met de geheime sleutel van de server. De server hoeft niets te onthouden; hij hoeft alleen de handtekening van het binnenkomende token te verifiëren. Daarom heet JWT stateless authenticatie, en precies daarom is het zo gangbaar in microservices, mobiele apps en API's.
De drie delen van een token
Een JWT bestaat uit drie delen, gescheiden door punten: header.payload.signature. Elk deel is Base64Url-gecodeerd (gecodeerd, niet versleuteld — een belangrijk onderscheid).
- Header: geeft het tokentype en het gebruikte ondertekeningsalgoritme aan. Voorbeeld:
{"alg":"HS256","typ":"JWT"}. - Payload: draagt de zogenaamde claims — bijvoorbeeld de gebruikers-id (
sub), de vervaltijd (exp), het aanmaaktijdstip (iat) en velden die je zelf toevoegt (zoals een rol). - Signature: de header en payload ondertekend met de geheime sleutel. Dit deel garandeert dat het token niet is gewijzigd.
Een echt token ziet er zo uit:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
.eyJzdWIiOiIxMjMiLCJyb2xlIjoiYWRtaW4iLCJleHAiOjE3MTk1MDAwMDB9
.s5_3kF9vQk2...handtekening
Een cruciaal punt: de payload is niet versleuteld. Iedereen kan Base64Url decoderen en lezen. Zet daarom nooit geheimen zoals wachtwoorden of creditcardnummers in een token. Een token draagt "identiteit", geen geheimen.
Hoe werkt het ondertekenen?
De handtekening is het hart van het hele beveiligingsmodel van JWT. Bij het HS256-algoritme (HMAC + SHA-256) wordt de handtekening zo berekend:
HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
geheimeSleutel
)
Wanneer de server een token verifieert, herhaalt hij precies dezelfde bewerking: hij ondertekent de binnenkomende header en payload opnieuw met zijn eigen geheime sleutel en vergelijkt het resultaat met de handtekening in het token. Probeert iemand de rol in de payload van user naar admin te veranderen, dan klopt de handtekening niet meer, want de aanvaller heeft de geheime sleutel niet. Daarom is het token "onvervalsbaar".
Er zijn twee benaderingen van ondertekenen:
- HS256 (symmetrisch): één geheime sleutel ondertekent én verifieert. Eenvoudig en snel, maar wie kan verifiëren kan ook tokens uitgeven.
- RS256 (asymmetrisch): ondertekent met een privésleutel, verifieert met een publieke sleutel. Ideaal als meerdere services tokens moeten verifiëren maar ze niet mogen uitgeven.
In de praktijk: een token uitgeven en verifiëren
In Node.js, met de bibliotheek jsonwebtoken, oogt de flow heel overzichtelijk:
const jwt = require('jsonwebtoken');
// Geef een token uit na een succesvolle login
const token = jwt.sign(
{ sub: user.id, role: user.role },
process.env.JWT_SECRET,
{ expiresIn: '15m' }
);
// Verifieer het bij volgende requests
try {
const payload = jwt.verify(token, process.env.JWT_SECRET);
// identificeer de gebruiker via payload.sub
} catch (err) {
// ongeldige handtekening of verlopen
}
De client stuurt dit token meestal in de header Authorization: Bearer <token>. Een middleware op de server leest die header bij elke request, verifieert hem en koppelt de gebruiker aan de request.
Het refresh token-patroon
Hier zit een spanning: je wilt dat het token kortlevend is (zodat de schade beperkt blijft als het wordt gestolen), maar de gebruiker elke 15 minuten opnieuw laten inloggen is een slechte ervaring. De oplossing is twee tokens gebruiken:
- Access token: kortlevend (bijvoorbeeld 15 minuten). Wordt bij elke API-request meegestuurd.
- Refresh token: langlevend (bijvoorbeeld 7 dagen). Wordt alleen gebruikt om een nieuw access token te krijgen.
Wanneer het access token verloopt, stuurt de client het refresh token naar de server en krijgt in ruil een vers access token — de gebruiker merkt er niets van. De belangrijke beveiligingsdetails:
- Je moet het refresh token aan serverzijde opslaan (of een blocklist bijhouden) zodat "uitloggen" het echt kan intrekken. Een puur access token is stateless en blijft dus geldig tot het verloopt.
- Het refresh token in een HttpOnly-cookie in de browser bewaren voorkomt dat JavaScript het kan lezen en via XSS stelen.
- Bij elk gebruik een nieuw refresh token uitgeven en het oude ongeldig maken (rotation) verkleint het risico op diefstal sterk.
Veelgemaakte fouten
- Geheimen in de payload zetten: de payload is openbaar, vergeet dat niet.
- Geen vervaltijd (
exp) instellen: een token zonder vervaltijd blijft na diefstal voor altijd geldig. alg: noneaccepteren: configureer je bibliotheek zo dat ze expliciet aangeeft welke algoritmen ze accepteert.- De geheime sleutel hardcoden: de sleutel hoort in een omgevingsvariabele (
.env), niet in de repo.
Veelgestelde vragen
Is een JWT versleuteld?
Nee. Standaard is een JWT ondertekend, niet versleuteld. De inhoud is leesbaar maar kan niet worden gewijzigd. Wil je de inhoud ook verbergen, dan is JWE (JSON Web Encryption) een aparte standaard — maar in de meeste gevallen is het juiste om gevoelige data simpelweg nooit in het token te zetten.
Waar moet ik het token opslaan?
Er is geen enkel perfect antwoord. localStorage is makkelijk maar kwetsbaar voor XSS. Een HttpOnly-cookie is beschermd tegen XSS maar vereist een CSRF-bescherming. In mobiele apps gebruik je bij voorkeur veilige opslag (Keychain/Keystore).
Kan ik een afzonderlijke JWT intrekken?
Een pure JWT is stateless en kan dus niet rechtstreeks worden ingetrokken; hij blijft geldig tot hij verloopt. Heb je intrekking nodig, dan gebruik je een kortlevend access token plus een refresh token aan serverzijde (blocklist).
Authenticatie goed opzetten is de basis van de beveiliging van een project. Heb je hulp nodig bij het opzetten van een JWT, een refresh token-flow of een veilige API-architectuur in het algemeen, neem dan contact met me op — laten we samen een stevige basis leggen.