JSON Web Tokens (JWT’s) worden veel gebruikt voor authenticatie van API’s en voor single sign-on. Het formaat is eenvoudig, maar laat veel beveiligingskeuzes over aan de ontwikkelaar en de bibliotheek. RFC 8725, de JWT Best Current Practices, verzamelt de bekende fouten en hoe u ze vermijdt. In dit artikel lopen we de fouten langs die wij als eerste zouden controleren.
Een JWT is ondertekend, niet geheim
Een ondertekend JWT, gedefinieerd in RFC 7519, bestaat uit drie delen, gescheiden door punten: een header, een payload met claims en een handtekening. De eerste twee zijn alleen base64url-gecodeerd. Iedereen die het token heeft, kan ze lezen. Hier is een gedecodeerd voorbeeld:
{
"alg": "RS256",
"typ": "at+jwt",
"kid": "2026-10-key-1"
}
{
"iss": "https://auth.example.eu",
"sub": "user-4821",
"aud": "api.example.eu",
"iat": 1792140000,
"exp": 1792140900,
"role": "editor"
}
De handtekening bewijst dat de claims niet zijn gewijzigd. Ze verbergt ze niet. RFC 7519 stelt dat als een token privacygevoelige informatie bevat, er maatregelen moeten worden genomen om openbaarmaking te voorkomen, bijvoorbeeld door een versleuteld JWT (JWE) te gebruiken. Houd in de praktijk persoonsgegevens, interne kenmerken die u niet zou publiceren en alles wat geheim is buiten de payload.
Valkuil 1 - "alg": "none" accepteren
RFC 7519 staat "onbeveiligde" JWT’s toe met "alg": "none" en een lege handtekening. RFC 8725 beschrijft bibliotheken die zulke tokens accepteerden en de controle van de handtekening helemaal oversloegen. Een aanvaller kon dan elke gewenste claim invullen.
RFC 7518 bepaalt dat implementaties onbeveiligde tokens standaard niet mogen accepteren, en de OWASP REST Security Cheat Sheet zegt ze helemaal niet toe te staan. Controleer of uw bibliotheek none weigert, en test dat met een zelfgemaakt token.
Valkuil 2 - het token het algoritme laten kiezen
De waarde alg in de header wordt gekozen door wie het token heeft gemaakt, en dat kan een aanvaller zijn. RFC 8725 beschrijft de klassieke verwarringsaanval: een server verwacht RS256, geverifieerd met een publieke RSA-sleutel. De aanvaller verandert alg in HS256 en ondertekent het token met HMAC, met die publieke sleutel als geheim. Een bibliotheek die de header volgt, verifieert het token met succes, en de publieke sleutel is per definitie openbaar.
De oplossing is het algoritme op de server vast te leggen. Volgens OWASP mag de verificatie niet op de JWT-header vertrouwen om het algoritme te kiezen. RFC 8725 voegt daaraan toe dat elke sleutel met precies één algoritme moet worden gebruikt. In code ziet dat er ongeveer zo uit:
verify(token,
key = issuer_public_key,
algorithms = ["RS256"],
issuer = "https://auth.example.eu",
audience = "api.example.eu")
Valkuil 3 - zwakke HMAC-geheimen
Bij HS256 kan iedereen die een token onderschept offline proberen het geheim te raden, zo snel als de hardware toestaat. RFC 8725 bepaalt dat wachtwoorden die mensen kunnen onthouden niet rechtstreeks als HMAC-sleutel mogen worden gebruikt. RFC 7518 eist een sleutel die minstens zo lang is als de uitvoer van de hashfunctie, dus 256 bits voor HS256.
openssl rand -base64 32
Dat levert 32 willekeurige bytes op, oftewel 256 bits. Bewaar die in een secrets manager, niet in de repository.
Gedeelde geheimen hebben nog een zwakte. Elke dienst die een HMAC-token kan verifiëren, kan er ook een maken. Daarom geeft OWASP de voorkeur aan handtekeningen boven MAC’s: als meerdere diensten tokens valideren, kan één gecompromitteerde dienst anders tokens voor alle diensten aanmaken. Met asymmetrische algoritmen zoals RS256 of ES256 hoeft u alleen de publieke sleutel te delen.
Valkuil 4 - claims niet valideren
Een geldige handtekening bewijst alleen wie het token heeft uitgegeven. De claims bepalen of deze dienst het token nu moet accepteren.
exp- volgens RFC 7519 mag het token op of na dit tijdstip niet meer worden geaccepteerd. Een kleine marge voor klokverschillen, meestal niet meer dan een paar minuten, is toegestaan.nbf- weiger het token vóór dit tijdstip, als de claim aanwezig is.iss- controleer of de uitgever degene is die u vertrouwt en of die eigenaar is van de ondertekeningssleutel.aud- volgens RFC 7519 moet het token worden geweigerd als de dienst zichzelf niet in de doelgroep terugvindt.
OWASP raadt aan om voor toegangstokens van API’s standaard iss, aud en exp te eisen en te valideren. RFC 8725 legt uit waarom de doelgroep belangrijk is. Zonder doelgroep kan een token dat voor de ene dienst is uitgegeven, opnieuw worden gebruikt bij een andere dienst die dezelfde uitgever vertrouwt. RFC 8725 raadt ook expliciete typering aan met de header typ, zodat het ene soort token niet kan worden gebruikt waar een ander soort wordt verwacht. Voor OAuth-toegangstokens gebruikt RFC 9068 at+jwt, zoals in het voorbeeld hierboven.
Valkuil 5 - kid, jku en x5u vertrouwen
Headerparameters die de server vertellen waar de sleutel te vinden is, worden ook door de aanvaller bepaald. RFC 8725 noemt de risico’s:
kid(sleutel-ID) die wordt gebruikt bij een opzoeking in een database of directory, kan SQL- of LDAP-injectie mogelijk maken. Hetzelfde geldt als hij wordt gebruikt om een bestandspad op te bouwen.jkuenx5uverwijzen naar een URL om sleutels op te halen. Elke URL volgen maakt server-side request forgery mogelijk, en een aanvaller kan de URL naar zijn eigen sleutel laten wijzen.
Behandel kid als een betekenisloos label en zoek het op in een vaste lijst sleutels die u zelf beheert. Haal sleutels alleen op van URL’s op een allowlist; RFC 8725 voegt daaraan toe dat bij die verzoeken geen cookies mogen worden meegestuurd.
Valkuil 6 - lang geldige tokens en geen intrekking
Een JWT blijft geldig tot het verloopt, ook nadat de gebruiker is uitgelogd of een beheerder het account heeft uitgeschakeld. OWASP merkt op dat tokens langer kunnen bestaan dan de sessie die ze vertegenwoordigen.
- Houd toegangstokens kortlevend, gemeten in minuten in plaats van dagen.
- Gebruik refreshtokens die de server bij elk gebruik controleert en kan intrekken.
- Houd voor uitloggen en het blokkeren van accounts een denylist bij van token-ID’s (
jti) totdat die tokens verlopen, zoals OWASP voorstelt. - Vervang ondertekeningssleutels volgens een vast schema, en zorg voor een getest proces voor een vervanging in noodgevallen.
Valkuil 7 - tokens opslaan waar scripts ze kunnen lezen
Tokens in localStorage zijn leesbaar voor elk JavaScript op de pagina. De OWASP HTML5 Security Cheat Sheet waarschuwt dat één enkele cross-site-scriptingfout alles in deze opslagobjecten kan stelen, en raadt af om er sessie-ID’s in te bewaren.
Voor browserapplicaties houdt een cookie met HttpOnly en Secure en een passende SameSite-instelling het token buiten bereik van scripts. Met cookies komt cross-site request forgery in beeld, dus houd daar ook rekening mee. Wat u ook kiest: een sterke Content Security Policy verkleint de kans op XSS in de eerste plaats.
Een checklist voor validatie
- Leg de geaccepteerde algoritmen op de server vast en weiger
none. - Gebruik een bibliotheek die de handtekening verifieert voordat u claims leest.
- Valideer
exp,nbf,issenaudbij elk verzoek. - Gebruik
typof aparte doelgroepen om soorten tokens van elkaar te scheiden. - Zoek sleutels op
kidop in een vaste set; haal nooit sleutels op van willekeurige URL’s. - Gebruik willekeurige HMAC-sleutels van minstens 256 bits, of asymmetrische sleutels.
- Houd payloads vrij van geheimen en onnodige persoonsgegevens.
- Houd toegangstokens kortlevend en zorg dat u ze kunt intrekken.
- Bewaar tokens in de browser buiten bereik van JavaScript.
Waar u begint
Plak een token uit uw testomgeving in onze JWT-decoder om precies te zien wat het prijsgeeft, en toets de waarden van alg, aud en exp aan deze lijst. Plak geen actieve productietokens in welke onlinetool dan ook.
Wilt u een second opinion over het ontwerp van uw authenticatie of een codereview van de manier waarop u met tokens omgaat? Ons team voor veilige software helpt u graag.
