# JWT

## Hvad det er

Et JWT er en kort tekststreng, der bærer en række udsagn fra én part til en
anden i en form, modtageren kan kontrollere. Bogstaverne står for JSON Web
Token. RFC 7519 definerede formatet i maj 2015 som en kompakt måde at
repræsentere claims på, der kan sendes uændret i en URL, og foreslår, at navnet
udtales som det engelske ord »jot«. Et claim er et navn og en værdi, der siges
om et subjekt: `"sub": "24400320"` fortæller, hvem tokenet handler om, og
`"exp": 1300819380` fortæller, hvornår det holder op med at gælde.

Når det sendes, er et JWT en række base64url-strenge adskilt af punktummer. Et
signeret JWT har tre: en header, der angiver algoritmen, en payload med claims
og en signatur beregnet over de to første. Et krypteret JWT har fem, og dets
claims kan ikke læses uden nøglen. RFC 7519 står ikke selv for kryptografien.
Den lader JSON Web Signature (RFC 7515) tage sig af tokens, der signeres eller
beskyttes med en MAC (message authentication code), og JSON Web Encryption (RFC
7516) af dem, der krypteres. Algoritmerne er listet i RFC 7518, og nøglerne kan
offentliggøres i formatet JSON Web Key fra RFC 7517.

`HS256` er en HMAC med SHA-256: én delt hemmelighed både laver og kontrollerer
tokenet, så enhver tjeneste, der kan verificere det, også kan udstede det.
`RS256` og `ES256` bruger et nøglepar: udstederen signerer med en privat nøgle,
og alle andre verificerer med den offentlige, som frit kan deles ud. RFC 7519
kræver kun, at `HS256` og den usignerede `none` er implementeret, og anbefaler
desuden `RS256` og `ES256`.

Afsnit 4.1 registrerer syv claimnavne: `iss` (udsteder), `sub` (subjekt), `aud`
(modtager, på engelsk audience), `exp` (udløbstidspunkt), `nbf` (gyldig fra),
`iat` (udstedt) og `jti` (et entydigt ID, der kan bruges til at forhindre, at
tokenet afspilles igen). Navnene er på tre bogstaver, fordi formatet skal være
kompakt, og de er alle valgfrie. Hvilke claims et token skal indeholde, afgør
den applikation, der bruger det. Ud over de registrerede kan enhver definere
offentlige claims, enten registreret hos IANA eller med et navn, der ikke kan
kollidere med andres, for eksempel en URL, og private claims, som to parter
aftaler indbyrdes.

## Hvorfor det betyder noget

Pointen med et JWT er, at modtageren kan kontrollere det på egen hånd. Med
udstederens nøgle og et ur kan en tjeneste afgøre, om tokenet er ægte, hvem det
er til, og om det stadig gælder, uden at slå op i en database og uden at spørge
udstederen. RFC 9068 fra oktober 2021 konstaterer, at mange kommercielle
OAuth-implementeringer udsteder adgangstokens, som ressourceservere selv parser
og validerer, at mønstret er særlig udbredt, hvor den server, der udsteder
tokens, og den, der modtager dem, er adskilt af en eller anden grænse, og at
mange af dem bruger JWT. Derefter standardiserede RFC'en opbygningen, så den
slags tokens fungerer på tværs af leverandører.

JWT er også formatet bag OpenID Connect: hvert ID-token er et JWT, signeret af
identitetsudbyderen og stilet til den applikation, der bad om det. Da
medietypen `application/jwt` blev registreret i 2015, nævnte registreringen
allerede OpenID Connect, Salesforce, Google, Android, Windows Azure og Amazon
Web Services blandt brugerne.

RFC 7519 sigter mod steder med begrænset plads, som HTTP-headeren Authorization
og parametre i en URI. Derfor er claimnavnene korte, og derfor bruger kodningen
kun tegn, der kan stå i en URL uden at blive escapet.

Den egenskab, der gør et JWT billigt at kontrollere, gør det også svært at
trække tilbage. En modtager, der kontrollerer signaturen og uret, accepterer
tokenet indtil `exp`, uanset hvad der er sket med kontoen i mellemtiden.

## Sådan ser det ud i praksis

RFC 7519 gennemgår et komplet eksempel. Headeren erklærer, at objektet er et JWT,
og at det er beskyttet med HMAC SHA-256:

```text
{"typ":"JWT",
 "alg":"HS256"}
```

Claims angiver en udsteder, et udløbstidspunkt og ét offentligt claim, hvis navn
er en URL, så det ikke kan støde sammen med et claim, som andre definerer:

```text
{"iss":"joe",
 "exp":1300819380,
 "http://example.com/is_root":true}
```

Hver del kodes med base64url, HMAC'en beregnes over header og payload sat
sammen med et punktum, og de tre resultater sættes sammen til én streng. RFC'en
viser den med linjeskift, der kun er der af hensyn til læsbarheden:

```text
eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9
.
eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFt
cGxlLmNvbS9pc19yb290Ijp0cnVlfQ
.
dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
```

Tre ting følger af eksemplet. For det første kan alle, der har tokenet, læse
dets claims: base64url er en kodning, så den midterste del bliver til JSON igen
uden nogen nøgle. For det andet dækker signaturen de kodede strenge præcis, som
de blev sendt. Headeren i RFC'en indeholder et CRLF-linjeskift og et indledende
mellemrum, og RFC'en angiver de nøjagtige bytes for at undgå tvivl. Formaterer
man JSON'en pænere og koder den igen, får man en anden streng, og signaturen
passer ikke længere. Nøglen til eksemplet står i appendiks A.1 i RFC 7515, og
med den verificerer signaturen. For det tredje er tokenet for længst udløbet:
`1300819380` sekunder efter 1. januar 1970 er 22. marts 2011 kl. 18.43 UTC, så
en korrekt validator afviser det i dag.

En modtager, der får sådan et token, kører en fast række kontroller, før den
læser et eneste claim. Den deler strengen ved punktummerne, afkoder headeren og
sikrer sig, at den er gyldig JSON med kun parametre, den forstår. Den
sammenligner `alg` med de algoritmer, den har besluttet at acceptere for netop
denne udsteder og nøgle. RFC 7519 er udtrykkelig om, at det er applikationens
beslutning, og at et token bør afvises, hvis algoritmen ikke er acceptabel, også
selvom signaturen er gyldig. Den verificerer signaturen med en nøgle, der hører
til udstederen. Derefter kontrollerer den claims: `iss` skal være en udsteder,
den stoler på, `aud` skal nævne modtageren selv, tidspunktet skal ligge før
`exp`, med højst et par minutters margin for forskelle mellem urene, og efter
`nbf`, hvis det claim er med. Først da handler den på indholdet.

Et JWT-adgangstoken efter RFC 9068 viser, hvad en konkret profil lægger til.
Headeren i den RFC lyder `{"typ":"at+JWT","alg":"RS256","kid":"RjEwOwOA"}`: en
udtrykkelig type, en asymmetrisk algoritme og ID'et på nøglen i udstederens
offentliggjorte nøglesæt. Profilen gør `iss`, `exp`, `aud`, `sub`, `client_id`,
`iat` og `jti` obligatoriske, og en ressourceserver skal afvise et token, hvis
type er andet end `at+jwt`.

Claims i et token er et øjebliksbillede fra det tidspunkt, det blev signeret, så
alt, hvad der kan ændre sig før `exp`, er bedre afgjort på serveren. I et af
sitets projekter,
[hærdning af en applikation i hvert lag](/da/portfolio/hardened-the-application-with-nonce-based-csp/),
kontrolleres rettighederne (tier, rolle, organisation og afgrænsning til skibe)
igen på serveren ved hver request, mens kontrollerne i frontenden kun betragtes
som UX.

## Hvor det går galt

**Tokenet vælger selv sin algoritme.** Headeren `alg` er der for fleksibilitetens
skyld, og RFC 8725 beskriver, hvordan nogle biblioteker stolede for meget på
den. En angriber kunne ændre den til `none`, og biblioteket accepterede tokenet
helt uden signatur. Eller angriberen ændrede `RS256` til `HS256`, og biblioteket
kontrollerede en HMAC med udstederens offentlige RSA-nøgle som delt hemmelighed,
en nøgle, angriberen bare kan hente. Konfigurér biblioteket med de algoritmer,
I accepterer, brug hver nøgle med præcis én algoritme, og afvis `none` helt.

**HMAC-hemmeligheden er en adgangskode.** Et token signeret med `HS256` under en
kort hemmelighed, der er til at huske, kan angribes offline: den, der har ét
eneste token, kan prøve gæt mod det i sit eget tempo uden at røre jeres server.
RFC 8725 forbyder at bruge en adgangskode, et menneske kan huske, direkte som
HMAC-nøgle, og RFC 7518 kræver en nøgle på mindst 256 bit til `HS256`. Generér
nøglen med en kryptografisk tilfældighedskilde, opbevar den i et
hemmelighedslager, og vælg et nøglepar, når mange tjenester kun skal verificere.

**Signaturen holder, så alt andet bliver troet.** En gyldig signatur viser, hvem
der udstedte tokenet, ikke hvem det var til. Hvis én udsteder betjener flere
tjenester, og modtageren springer `aud` over, kan et token, der lovligt blev sendt
til én tjeneste, genbruges mod en anden. RFC 8725 kalder det substitution og
kræver, at modtagere af den slags tokens validerer audience og afviser et token
uden. Kontrollér `aud` mod jeres egen identifikator, og kontrollér, at
signeringsnøglen hører til den udsteder, der står i `iss`.

**Én slags token går for en anden.** Et ID-token, et adgangstoken og et security
event token fra samme udsteder kan ligne hinanden meget, og en modtager, der kun
kontrollerer signatur og udsteder, kan acceptere den forkerte slags. RFC 8725
kalder det cross-JWT confusion og anbefaler en udtrykkelig type i headeren
`typ`. RFC 9068 bruger netop det med `at+jwt`. Giv hver slags token sin egen type
eller audience, og skriv valideringsreglerne, så hver slags falder igennem i de
andres kontroller.

**Headeren vælger nøglen.** Headeren `kid` angiver, hvilken nøgle der skal
bruges, og `jku` og `x5u` kan pege på en URL, nøglen skal hentes fra. De kommer
alle fra afsenderen. Et `kid`, der sendes ukontrolleret videre til en
databaseforespørgsel eller et opslag i en katalogtjeneste, bliver en vej ind i
det system, og en server, der følger enhver URL i `jku` eller `x5u`, kan
afsenderen få til at sende forespørgsler hvorhen som helst. Slå kun `kid` op
blandt nøgler, I allerede har, og hent kun nøgler fra en godkendt liste af
adresser, for eksempel udstederens offentliggjorte `jwks_uri`.

**Alle kan læse payloaden.** Et signeret JWT beskytter sine claims mod
ændringer, ikke mod at blive læst, og det kommer ofte forbi browsere, logfiler
og proxyer. RFC 7519 bemærker, at et JWT kan indeholde personfølsomme
oplysninger, og at den enkleste beskyttelse er at lade dem blive ude. Ellers bør
tokenet krypteres eller i det mindste kun sendes over TLS. RFC 9068 tilføjer, at
en klient slet ikke må se ind i et adgangstoken, fordi udstederen kan skifte
format når som helst. Læg identifikatorer i tokenet, ikke personoplysninger, og
kryptér, når selve indholdet skal holdes privat.

## Læs mere

- [RFC 7519: JSON Web Token (JWT)](https://www.rfc-editor.org/rfc/rfc7519) —
  selve formatet med de registrerede claims, det gennemgåede eksempel og
  valideringstrinnene.
- [RFC 7515: JSON Web Signature (JWS)](https://www.rfc-editor.org/rfc/rfc7515) —
  hvordan header, payload og signatur kodes og sættes sammen, og nøglen til
  eksemplet ovenfor.
- [RFC 8725: JSON Web Token Best Current Practices](https://www.rfc-editor.org/rfc/rfc8725)
  — de kendte angreb på JWT og reglerne for biblioteker og applikationer, der
  forhindrer dem.
- [RFC 7518: JSON Web Algorithms (JWA)](https://www.rfc-editor.org/rfc/rfc7518)
  — algoritmenavnene i `alg` med mindste nøglestørrelse for hver HMAC.
- [RFC 9068: JWT Profile for OAuth 2.0 Access Tokens](https://www.rfc-editor.org/rfc/rfc9068)
  — standardopbygningen af JWT-adgangstokens og de kontroller, en
  ressourceserver skal køre.

<https://engineer.company/da/glossary/jwt/>
