☎ 06 45 677 645 ✉ info@flowmesh.nl
Blog

JWT-beveiliging: van token naar architectuurbeslissing

JSON Web Tokens zijn de standaard voor authenticatie in moderne webapplicaties. Vrijwel elk framework ondersteunt ze, de implementatie is rechttoe rechtaan, en binnen een uur heeft u een werkende login. Maar dáár begint het eigenlijk pas.

Want de vraag is niet of u JWT gebruikt, maar hoe. En die hoe-vraag raakt aan fundamentele architectuurbeslissingen die het verschil maken tussen een veilige applicatie en een applicatie die wacht op een incident.

De stateless-illusie

JSON Web Tokens worden vaak gepresenteerd als een manier om authenticatie "stateless" te maken. Dat is echter een misleidende versimpeling. Een JWT maakt hooguit de validatie van een individueel request stateless: de server kan de handtekening controleren en claims lezen zonder een sessie op te zoeken. Maar identiteit zelf — inclusief accountstatus, rechten, sessiebeheer en revocatie — is in iedere serieuze applicatie per definitie stateful. Een token is daarmee geen bron van waarheid, maar een cache van eerder vastgestelde identiteit en autorisatie: een cryptografisch bewijs van wat op het moment van uitgifte geldig was. De architectuurvraag verschuift daarmee van “hoe valideer ik dit token?” naar “in hoeverre vertrouw ik deze momentopname nog, en wanneer grijp ik terug op de onderliggende state?”

De keuze hoe een token wordt gevalideerd is minstens zo bepalend. Volstaat een pure signature-check, of wordt bij elke request aanvullende state geraadpleegd, zoals accountstatus of revocatie? Die keuze bepaalt of een JWT een zelfstandig bewijs is, of slechts een hint die door de server wordt bevestigd.

Het probleem zit niet in het token

Een correct geïmplementeerde JWT is cryptografisch solide — mits de server alleen expliciet gewhiteliste algoritmen accepteert (RS256, ES256) en aanvalsklassen als alg=none en HS↔RS key confusion zijn uitgesloten. De kwetsbaarheden ontstaan in de keuzes eromheen: hoe vervoert u het token van client naar server? Waar slaat de browser het op? En wat gebeurt er als uw applicatie bestanden moet serveren vanuit S3 of CloudFront?

Elke combinatie van transport en opslag heeft andere eigenschappen op het gebied van XSS, CSRF en token-exfiltratie. Die matrix is niet intuïtief — en foute keuzes zijn moeilijk te herstellen als de applicatie eenmaal in productie is.

Transport: meer opties dan u denkt

De meeste developers kennen twee smaken: de Authorization: Bearer header en de HttpOnly cookie. Maar er zijn er meer, elk met eigen afwegingen:

  • Authorization header — CSRF-immuun, de standaard voor REST en GraphQL. Maar vereist JavaScript, waardoor u er geen afbeeldingen of PDF's mee kunt laden.
  • HttpOnly cookie — automatisch meegestuurd bij alle requests, inclusief afbeeldingen en iframes. Niet leesbaar via JavaScript, dus XSS-resistent. Maar introduceert CSRF als primaire attack vector zodra SameSite niet strikt is geconfigureerd.
  • Service Worker proxy — het meest onderschatte patroon. Een Service Worker onderschept alle browser-requests en injecteert de header, inclusief voor media en downloads. Het token is niet direct leesbaar via pagina-JavaScript, maar een Service Worker is geen harde security boundary tegen XSS.

En dan zijn er de patronen die u beter kunt vermijden: tokens in query parameters (zichtbaar in logs, browser history en Referer headers) of in localStorage (volledig leesbaar bij elke XSS-kwetsbaarheid).

Opslag: niet elke plek is gelijk

Waar u het token client-side bewaart, bepaalt direct uw aanvalsoppervlak:

Opslag XSS-risico Kenmerken
JavaScript-variabele (memory) Veilig Verdwijnt bij refresh. Ideaal als access token, gecombineerd met een HttpOnly refresh cookie.
Service Worker memory Resistent Aparte thread, niet bereikbaar via pagina-JavaScript.
HttpOnly cookie Resistent Niet via JS leesbaar. Gebruik __Host- prefix voor sterkste isolatie.
localStorage Kwetsbaar Persistent, cross-tab, volledig JS-leesbaar. Eén XSS-kwetsbaarheid en het token is weg.
sessionStorage Kwetsbaar Tab-scoped, maar nog steeds XSS-kwetsbaar. Gebruik memory.

De productie-aanbeveling is helder: refresh token in een HttpOnly, Secure, SameSite=Strict cookie. Access token in JavaScript memory met een korte TTL van 15 minuten. Valideer met een clock skew tolerantie van ±30–60 seconden op exp, en controleer nbf indien aanwezig. API-calls via Authorization: Bearer.

Cross-domain en CDN: waar het complex wordt

Zodra uw applicatie bestanden serveert vanuit S3, R2 of CloudFront, wordt de JWT-architectuur een stuk complexer. Een Authorization header kan niet automatisch door de browser worden meegestuurd bij <img>-tags of directe downloads. U heeft dan andere patronen nodig:

  • Pre-signed URLs — de standaard voor S3 en R2. HMAC-gesigneerde URLs met een TTL van maximaal 15 minuten en bij voorkeur een single-use nonce. Geen JWT op de client nodig.
  • CloudFront signed cookies — één set cookies voor wildcard-path toegang. Ideaal als een gebruiker veel bestanden per sessie opvraagt.
  • Cloudflare Worker als proxy — valideert de JWT op de edge via Web Crypto, haalt het bestand op uit R2 en streamt naar de client. Nul exposure van bucket credentials.
  • Fetch naar Blob URL — voor inline weergave van afbeeldingen of PDF's. Fetch met Bearer header, converteer naar Blob, toon via createObjectURL. De veiligste optie voor beveiligde media.

Geavanceerde patronen

Voor applicaties met hoge beveiligingseisen zijn er aanvullende technieken:

  • DPoP (RFC 9449) — bindt het token aan een client keypair. Een gestolen token is waardeloos zonder de bijbehorende private key. In PKI- en enterprise-omgevingen biedt mTLS (RFC 8705) een nog sterkere binding op transportniveau, maar dit is praktisch niet toepasbaar in browserapplicaties.
  • Token rotation met family-invalidation — refresh tokens zijn eenmalig bruikbaar. Wordt een token hergebruikt, dan wordt de hele token-familie ongeldig. Diefstal-detectie ingebouwd.
  • Silent refresh — een background fetch naar /auth/refresh vervangt het verouderde iframe-patroon. Cookie autoriseert, access token komt in de response body, opgeslagen in memory. Omdat het refresh endpoint autoriseert via een HttpOnly cookie, is het inherent CSRF-gevoelig — mitigeer met SameSite=Strict, een CSRF-token (double submit) of Origin/Referer-validatie.

Alle patronen, risico's en aanbevelingen staan in de reference card — één pagina met de complete matrix van transport × opslag × resource type.

Download de reference card (PDF)

Waarom dit ertoe doet

Token-beveiliging is geen checkbox op een compliance-lijst. Het is een architectuurbeslissing die vroeg in het ontwikkelproces gemaakt moet worden en die doorwerkt in elke laag van uw applicatie. De kosten van een verkeerde keuze zijn hoog: een herstructurering van authenticatie in een draaiende applicatie is een van de duurste wijzigingen die er is.

Bij Flowmesh bouwen we deze beveiligingspatronen vanaf dag één in de architectuur in. Niet als afterthought, maar als fundament.

Vrijblijvend contact opnemen

U ontvangt binnen één werkdag een reactie.

Paul Hoogendijk - Flowmesh

Paul Hoogendijk

Directeur & Principal Architect