Wat is een JSON Web Token (JWT)?
JSON Web Token (JWT) wordt veel gebruikt in moderne webapplicaties en open standaarden zoals OpenID Connect, en faciliteert authenticatie (Authentication) en autorisatie (Authorization). Hoewel de officiële RFC 7519 als een essentiële referentie dient, kan het voor beginners uitdagend zijn om te begrijpen. In dit artikel richten we ons op de kernconcepten van JWT en presenteren we ze in begrijpelijke taal met voorbeelden.
Waarom hebben we JWT nodig?
Tegenwoordig is het vrij gebruikelijk om JSON te gebruiken om gegevens tussen twee partijen uit te wisselen. Overweeg een JSON-object dat een gebruiker vertegenwoordigt:
{
"sub": "foo",
"name": "John Doe"
}
sub
is de afkorting voor “subject”, wat een standaardclaim is in OpenID Connect om de gebruiker identifier (gebruikers-ID) weer te geven.
Hoe kunnen we de integriteit van dit JSON-object garanderen? Met andere woorden, hoe kunnen we ervoor zorgen dat de gegevens tijdens de overdracht niet zijn gewijzigd? Een gebruikelijke oplossing is het gebruik van digitale handtekeningen. Bijvoorbeeld, we kunnen openbare-sleutelcryptografie gebruiken: de server ondertekent het JSON-object met zijn privésleutel, en de client kan de handtekening verifiëren met de openbare sleutel van de server.
Kortom, JWT biedt een gestandaardiseerde aanpak om het JSON-object en de bijbehorende handtekening te vertegenwoordigen.
JWT kan ook worden gebruikt om het JSON-object te versleutelen, maar dat is niet de focus van dit artikel.
Het formaat van JWT
Aangezien er veel algoritmen zijn voor het maken van digitale handtekeningen, is het noodzakelijk om het algoritme te specificeren dat voor JWT-ondertekening wordt gebruikt. Dit wordt bereikt door het construeren van een JSON-object:
{
"alg": "HS256",
"typ": "JWT"
}
alg
staat voor “algoritme”, entyp
staat voor “type”.
Typisch wordt typ
ingesteld op JWT
in hoofdletters. In ons voorbeeld is alg
HS256
, dat staat voor
HMAC-SHA256 (we zullen het zo uitleggen), en geeft aan dat we dit algoritme gebruiken om de handtekening te maken.
Nu hebben we alle ingrediënten voor een JWT:
- Header JSON: Algoritme en type
- Payload JSON: De eigenlijke data
- Handtekening: De handtekening die de header en payload omvat
Echter, bepaalde tekens zoals spaties en regeleinden zijn niet vriendelijk voor netwerkoverdracht. Daarom moeten de header en payload Base64URL-gecodeerd worden. De typische JWT ziet er zo uit:
{{header}}.{{payload}}.{{signature}}
De
.
dient als scheidingsteken.
Laten we alles samenvoegen en een JWT maken:
Header
JSON: {"alg":"HS256","typ":"JWT"}
Base64URL gecodeerd: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
Payload
JSON: {"sub":"foo","name":"John Doe"}
Base64URL gecodeerd: eyJzdWIiOiJmb28iLCJuYW1lIjoiSm9obiBEb2UifQ
Handtekening
In HMAC-SHA256 wordt de handtekening gemaakt met een geheim:
HMAC-SHA256(base64Url(header) + "." + base64Url(payload), secret)
Bijvoorbeeld, met het geheim als some-great-secret
, wordt de handtekening: XM-XSs2Lmp76IcTQ7tVdFcZzN4W_WcoKMNANp925Q9g
.
JWT
De uiteindelijke JWT is:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJmb28iLCJuYW1lIjoiSm9obiBEb2UifQ.XM-XSs2Lmp76IcTQ7tVdFcZzN4W_WcoKMNANp925Q9g
Deze geldige JWT kan worden geverifieerd door elke partij die het geheim bezit.
Kies het ondertekeningsalgoritme
Zoals eerder vermeld, zijn er verschillende algoritmen om digitale handtekeningen te maken. We hebben HS256
als voorbeeld gebruikt, maar het is mogelijk niet sterk genoeg omdat het geheim moet worden gedeeld tussen de partijen (bijvoorbeeld de client en de server).
In scenario’s uit de praktijk kunnen clients openbare applicaties bevatten, zoals React-apps, die het geheim niet veilig kunnen houden. Daarom is de voorkeursbenadering het gebruik van openbare-sleutelcryptografie (d.w.z. asymmetrische cryptografie) voor het ondertekenen van de JWT. Laten we beginnen met het meest populaire algoritme: RSA .
RSA
RSA, een asymmetrisch algoritme, gebruikt een paar van sleutels: een openbare sleutel en een privésleutel. De openbare sleutel wordt gebruikt om de handtekening te verifiëren, terwijl de privésleutel wordt gebruikt voor het ondertekenen.
Het header JSON voor RSA ziet er zo uit:
{
"alg": "RS256",
"typ": "JWT"
}
RS256
staat voor RSA-SHA256, wat betekent dat de handtekening wordt gemaakt met het RSA-algoritme en SHA256-hashfunctie. Je kunt ookRS384
enRS512
gebruiken om handtekeningen te maken met respectievelijk SHA384 en SHA512 hashfuncties.
De handtekening wordt gemaakt met de privésleutel:
RSA-SHA256(base64Url(header) + "." + base64Url(payload), privateKey)
Opnieuw kunnen we deze onderdelen samenvoegen om een JWT te maken, en de uiteindelijke JWT ziet er zo uit:
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJmb28iLCJuYW1lIjoiSm9obiBEb2UifQ.{{signature}}
Nu kan de client de handtekening verifiëren zonder de privésleutel te kennen.
ECDSA
Hoewel RSA veel toegepast wordt, heeft het last van grotere handtekeningformaten die soms de gecombineerde grootte van de header en payload overschrijden. Het Elliptic Curve Digital Signature Algorithm (ECDSA) is een ander asymmetrisch algoritme dat compactere handtekeningen kan maken en beter presteert.
Om een privésleutel voor ECDSA te genereren, moeten we een kromme kiezen. Dit valt buiten de scope van dit artikel, maar je kunt meer informatie vinden hier .
Het header JSON voor ECDSA ziet er zo uit:
{
"alg": "ES256",
"typ": "JWT"
}
ES256
staat voor ECDSA-SHA256, wat betekent dat de handtekening wordt gemaakt met het ECDSA-algoritme en SHA256-hashfunctie. Je kunt ookES384
enES512
gebruiken om handtekeningen te maken met respectievelijk SHA384 en SHA512 hashfuncties.
De handtekening wordt gemaakt met de privésleutel:
ECDSA-SHA256(base64Url(header) + "." + base64Url(payload), privateKey)
De uiteindelijke JWT behoudt dezelfde structuur als RSA maar met een aanzienlijk kortere handtekening:
eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJmb28iLCJuYW1lIjoiSm9obiBEb2UifQ.{{signature}}
Verifieer de JWT
Het verifiëren van een JWT is eenvoudig als het omgekeerde proces van het maken van een JWT:
- Splits de JWT in drie delen (header, payload en handtekening) met behulp van de
.
scheidingsteken. - Decodeer de header en payload met Base64URL.
- Verifieer de handtekening met het algoritme dat in de header is gespecificeerd en de openbare sleutel (voor asymmetrische algoritmen).
Er zijn veel libraries beschikbaar om te helpen met JWT-verificatie, zoals jose voor Node.js en webbrowsers.