Authenticatie in Single Page Applications en CSRF

  • Giel Oerlemans
  • vrijdag 27 juni 2025 om 15:16
Bij het maken van een webapplicatie zijn verschillende manieren van authenticatie mogelijk. De afweging welke te gebruiken hangt af van verschillende zaken, waaronder natuurlijk de veiligheidseisen, maar ook de verwachte belasting en daarmee gepaard gaande schaalbaarheid. 

overgang van cookie naar code

Cookies

Traditioneel werd herhaaldelijke authenticatie van webrequests gedaan met behulp van cookies. De gebruiker bewijst eenmalig (door middel van mijn wachtwoord) dat hij of zijn is wie diegene claimt te zijn (gebruikernaam). De server stuurt een cookie naar de browser. Bij iedere opvolgende request wordt de cookie meegestuurd en weet de server dat de identiteit eerder bewezen is en de gebruiker dus ‘ingelogd’ is. 

Nadeel van deze vorm van authenticatie is dat de server een ‘state’ bij moet houden, namelijk de ‘sessie’ van de gebruiker wiens cookie in een opvolgende request moet worden geaccepteerd. Dit zijn relatief kleine hoeveelheden gegevens, maar bij grote aantallen bezoekers kan dit een dermate zware belasting voor de server zijn, dat dit niet wenselijk is. Ook bij het inzetten van horizontale schaling (meer servers) wordt het uitdagend om de gebruiker iedere keer precies naar dezelfde server te sturen (waar de sessie loopt).

Cookies stelen

Een ander nadeel van cookies is dat, indien ze worden gestolen, de aanvaller de sessie direct kan overnemen. Een ‘simpele’ oplossing hiervoor is om cookies alleen toe te staan vanaf een bepaald IP adres en deze ongedaan te maken bij een request afkomstig vanuit een andere bron. Dit beschermt echter niet wanneer de aanvaller requests kan uitvoeren vanaf hetzelfde netwerk als het doelwit.

Het stelen van cookies kan op meerdere manieren plaatsvinden. Wanneer een verbinding niet beveiligd is, doordat er requests over HTTP gaan, dan kan de cookie ‘in transit’ gestolen worden. Is de client geïnfecteerd met malware of heeft een cybercrimineel op een andere manier toegang tot het systeem, dan kunnen cookies als ‘bestandjes’ van de harde schijf worden gestolen. Javascript heeft echter vaak ook toegang tot de cookies. Wanneer een aanvaller in staat is Javascript te injecteren (XSS aanval) in een bestaande webpagine, dan kan die Javascript-code de cookies van het slachtoffer stelen en deze bijvoorbeeld opsturen naar de aanvaller. Dit kan voorkomen worden door gebruik te maken van httpOnly cookies, die niet bereikbaar zijn vanaf Javascript. 

JSON Web Tokens

Dit blog gaat echter over SPA’s en CSRF… En ‘moderne’ webontwikkeling authenticeert request naar het backend vanuit Single Page (of reguliere Javascript) applicaties vaak door middel van JSON Web Tokens, oftewel JWT. Zo’n token wordt door de auhtenticatieserver uitgedeeld en ondertekend met een digitale handtekening. In het token staat alle benodigde informatie over de gebruiker, de echtheid is gegarandeerd door de authenticatieserver. Bij iedere request wordt dit token (doorgaans) mee gestuurd in een ‘Authorization’ header, vaak in de vorm:
Authorization: Bearer <token>

Voordeel is dat er geen state hoeft te worden bijgehouden en met behulp van het token op iedere server kan worden geauthenticeerd. Het token bevat namelijk de identeitsinformatie en de authenticiteit wordt bevestigd door de digitale handtekening van de uitgevende server.

JWT opslag

De vraag aan de developer: “waar laat je een JWT token?”, of beter: waar sla je deze op. Vaak wordt gebruik gemaakt van een browservoorziening, zoals local storage of session storage. Ook opslaan in het applicatie geheugen kan en is veiliger dan voorgaande twee opties, maar het token overleeft dan mogelijk een page refresh niet. 
Al deze locaties zijn echter (in bepaalde mate) vatbaar voor XSS aanvallen. Kan een aanvaller javascript injecteren, dan kunnen tokens worden gestolen. 

Een alternatief kan zijn om het token in een cookie op te slaan en het cookie door de browser mee te laten sturen. Een geslaagde XSS aanval kan dan in ieder geval geen token stelen. Bij het stelen van een token uit het hostsysteem kan het token overigens alsnog worden misbruikt maar wel vaak slechts voor beperkte duur. 

CSRF kwetsbaarheden

Een ander gevaar dat op de loer ligt bij het gebruik van cookies voor authenticatie: Cross-Site Request Forgery (CSRF). Collega Hans schreef er al eens een blog over. In een dergelijke aanval overtuigt een aanvaller het doelwit om een request uit te voeren. Omdat de browser automatisch de cookies mee zal sturen, voert het doelwit op die manier onbedoeld een geautoriseerde actie uit. Bij het opslaan van een JWT token in local storage speelt dit niet, want daar voegt de applicatiecode bij iedere request zelf het token toe in de header, de browser speelt daar geen rol in.

Om CSRF te voorkomen kan gebruik worden gemaakt van tokens, die toegevoegd moeten worden aan een request (op een soortgelijke manier als een bearer token) om aan te tonen dat de actie echt vanuit de gebruiker/applicatie komt. Deze CSRF tokens dienen dan natuurlijk ook in de clientside te worden opgeslagen, wat dezelfde afwegingen oplevert als het JWT token. Een alternatief is om voor iedere request een nieuw token op te vragen, wat de nodige overhead aan HTTP requests oplevert.

Conclusie

Dat leidt tot onder andere de volgende conclusies:
  • Gebruik je JWT tokens voor authenticatie en sla je die op in het (browser)geheugen, dan is een aanvullende CSRF beveiliging in de vorm van CSRF-tokens overbodig. Er bestaat echter een risico dat het JWT token in een XSS aanval wordt gestolen. 
  • Gebruik je httpOnly cookies om je token te communiceren, dan is er geen (bekende) mogelijkheid op diefstal van het token via XSS. Echter moet je dan beducht zijn op CSRF aanvallen en hier tegen beveiligen door het meesturen van een CSRF token in iedere request. 
    • Wordt dit CSRF token in het applicatiegeheugen opgeslagen en hergebruikt, dan kan het gestolen worden via XSS en ben je alsnog vatbaar voor een dergelijke aanval. 
    • Haal je voor (extra beveiligde) requests een nieuw token op (bijvoorbeeld gebruik maken van een HEAD request), dan kan de aanvaller niet beschikken over het nieuwe token (het doelwit doet namelijk de aanvraag en krijgt dus ook het antwoord) en is je applicatie weer niet vatbaar voor CSRF.

Om de impact van gestolen (JWT) tokens in te dammen, kan gebruik worden gemaakt van tokens met een korte expiration date en refresh tokens. 

En dan nog...

Uiteraard is het niet zo dat wanneer je JWT door gemaakte keuzes geen risico loopt om gestolen te worden in een XSS aanval, je niets meer tegen XSS zou moeten doen. Dat blijft op meerdere manieren een gevaar voor je applicatie en haar gebruikers. Het filteren van gebruikersinvoer moet natuurlijk altijd gebeuren, ter voorkoming van XSS, maar ook ter voorkoming  van allerhande andere aanvallen, waaronder SQL Injecties. Ook een strak afgestelde Content Security Policy is aan te raden. 

Voor de verdediging tegen CSRF zijn legio mogelijkheden, waarvan de primaire het CSRF token is. Er zijn echter meerdere maatregelen te nemen, waaronder het gebruik van het SameSite attribuut in cookies. Ingesteld op strikte mode, zal de browser geen cookie meesturen wanneer een cross-site request plaatsvindt. Dit brengt echter ook een bruikbaarheidsprobleem met zich mee.