Tokens ongeldig maken in gedistribueerde systemen
Op deze pagina
Snelle systemen bouwen is lastig en gedistribueerd rekenen is vaak de manier om de prestaties te verbeteren. Dat brengt eigen uitdagingen mee voor beveiliging, vooral bij authenticatie en autorisatie. De JSON Web Tokens van OAuth 2 lossen dat op, maar brengen risico’s mee bij het intrekken.
Gedistribueerde authenticatie bij SEEK
SEEK kent het gedistribueerde model goed. Bijna alles wat je ziet komt van tientallen microservices, in verschillende talen geschreven en door verschillende teams beheerd.
Sidecar: een samenhangende taak naast de hoofdapplicatie, meestal in een eigen proces of container, die een uniforme interface biedt aan services, ongeacht de taal.
Bij SEEK losten we dit op met een sidecar naast onze applicaties. De sidecar valideert tokens, accepteert of weigert binnenkomende verzoeken en geeft de context van de gebruiker door aan de applicatie.
Omdat applicaties bij SEEK in veel talen geschreven zijn, kozen we HTTP als uniforme interface. Bijna elke taal die we gebruiken kan het en met headers kun je een verzoek makkelijk aanvullen met extra informatie.
Je kunt de Authentication Sidecar ook zien als een proxy.
Een voorbeeld van het sidecar-patroon met twee takensets in een cloudomgeving die automatisch schaalt. Je ziet een geslaagd verzoek dat via de sidecar bij de applicatietaak aankomt en een ongeautoriseerd verzoek dat bij de sidecar stopt. De Authentication Sidecar gebruikt hier een gecachete JSON Web Key Set (JWKS) om de handtekening van de tokens te controleren. Zo kun je tokens gedistribueerd valideren zonder bij elk verzoek de Identity Provider aan te roepen.
Dit model is snel en schaalbaar, maar heeft een risico: een geblokkeerde gebruiker kan nog verzoeken doen tot het JSON Web Token (JWT) verloopt. De specificatie schrijft geen verlooptijd voor, maar in de praktijk zijn JWT’s kortlevend, ongeveer 15 minuten.
Tokens intrekken
Onderdeel van de OAuth 2-standaard is RFC7009, de Token Revocation-specificatie. Die beschrijft een vrij eenvoudig proces om tokens in te trekken.
De specificatie beschrijft een endpoint dat een client kan aanroepen met het token dat hij wil intrekken. Daarna worden validatieverzoeken voor dat token geweigerd, want het token geldt als ongeldig.
Deze specificatie werkt alleen met opaque tokens, niet met JSON Web Tokens. Die uitdaging speelt bij veel bedrijven, ook bij SEEK. In onze gedistribueerde architectuur is het extreem lastig om tokens efficiënt tegen één centrale bron te controleren. Daarom ondertekenen we tokens met een korte looptijd en accepteren we het risico dat een geblokkeerde gebruiker nog verzoeken kan doen tot dat token verloopt.
Een tweede uitdaging is een manier om het token te herkennen. JSON Web Tokens bevatten alle informatie om de gebruiker te identificeren. Ze worden samengesteld, ondertekend en verstuurd, niet opgeslagen. Er moet dus een extra claim bij om het token te identificeren. Daar dient de JWT ID voor.
Gedistribueerd tokens ongeldig maken
Tokens ongeldig maken in een gedistribueerd systeem is lastig, maar met een bloom filter kun je de overhead van het controleren van ingetrokken JWT ID’s tegen een centrale bron sterk verkleinen.
Tokens leven meestal kort en in geen enkel scenario verwachten we 100.000.000 tokens te moeten blokkeren. Die passen makkelijk in ~500MB geheugen als bloom filter, met een kans op een vals positief van 1 op 999.925.224.
Om dit scenario op te lossen werk ik aan oauth-revokerd. Deze service houdt een lijst van ingetrokken JWT ID’s bij en verspreidt daarvoor een bloom filter.
De service biedt een in-memory database om ingetrokken tokens kort op te slaan, tot het token zelf verloopt. Het idee is dat je de service zo snel mogelijk draait met minimale configuratie, met een beheer-API om tokens in te trekken en een query-API om intrekkingen te controleren bij een vals positief.
In dit concept lezen we de bloom filter ook in de sidecar.
Afsluiting
Dat is het voorlopig. Ik hoop in de toekomst meer met dit project te doen en post de voortgang op mijn website.