Stateless Sessions dank JSON Web Tokens.

06.10.2021 Ricky Elfner
Cloud Mobile Tech Rust api golang distributed-systems java microservices

JSON Web Token oder auch JWT ([dʒɒt]) genannt, werden für den Austausch von sogenannten Claims verwendet. Dies findet vor allem Anwendung im Bereich der Authentifizierung bei Mirco-Services und dienen als eine Alternative zu den klassischen Webcookies. JWT ist seit Mai 2015 auch ein offener und zertifizierter Standard(RFC 7519).

Was ist ein JWT?

Dafür beginnen wir zunächst einmal bei dem Grundprinzip eines Tokens. Dieser ist im Alltag mit einem Personalausweis vergleichbar und hilft einer Person, sich anhand der Einträge, die darin stehen, zu identifizieren. Dabei stammen Objekte, die der Authentifizierung gelten, immer von einer dritten und unabhängigen Instanz. Diese überprüft, ob diese Information auf einem Token, in diesem Fall der Personalausweis, korrekt sind. Wenn dies der Fall ist, gibt es eine Signierung.

Dieses Prinzip lässt sich auch auf die digitale Welt anwenden. Denn die zuvor erwähnten Einträgen werden in der digitalen Welt als Claims genannt. Durch dieses Verfahren ist nun keine Datenbankabfrage mehr nötig. Denn der Benutzer erhält nach einem erfolgreichen Login einen JWT, welcher alle wichtigen Informationen über den User enthält. Somit muss die Sitzung nicht mehr auf dem Server gespeichert werden und wird deshalb auch Stateless Session genannt.

Was ist JWS und JWE?

Wenn man es genau betrachtet, gibt es kein reinen JWT: Denn entweder handelt sich um ein JWS oder um ein JWE. Aus diesem Grund ist der JWT eigentlich eine Art abstrakte Klasse. JWS steht für JSON Web Signature und ist eine Implementation eines JWTs welches mit einem Secret Key verschlüsselt wird. Aber auch bei einem ungesicherten JWT handelt es sich um ein JWS, denn dabei ist der Verschlüsselung Algorithmus einfach auf none gesetzt.

Auf der anderen Seite gibt es dann noch JWE, was für JSON Web Encryption steht. Dieses Thema werden weitere unten separat betrachten. Des diese Variante besteht aus einem anderen Aufbau im Vergleich zu JWS.

Diese beiden zusätzlichen Definitionen gelten jedoch nur, solange es sich um “compact serialization” handelt. Sobald der grundlegende Aufbau eines JWTs besprochen wurde, werden wir auf den Punkt serialization näher eingehen.

Aufbau eines JWTs

Ein JSON Web Token besteht dabei aus drei Teilen:

  • einem Header,
  • einem Payload,
  • und der Signatur.

Der Header

Dieser wird als JSON-Objekt erstellt und enthält zwei Key-Value-Paare. Das erste Paar (alg) beschreibt den Algorithmus, mit dem die Signatur verschlüsselt wird. Hier stehen beispielsweise die Möglichkeiten RS256 oder HS256 zu Verfügung. Dabei könnten Sie jedoch auch auf eine Verschlüsselung verzichten, was jedoch auf keinen Fall zu empfehlen ist. Die zweite Property typ enthält immer hardcoded den Wert JWT. Dieser Header ist auch als JOSE-Header bekannt. Dabei ist JOSE die Abkürzung für Javascript Object Signing and Encryption. Die dazugehörige Datenstruktur sieht wie folgt aus:

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

Der Payload

Hier handelt es sich ebenfalls um ein JSON-Objekt, welches jedoch nun die eigentlichen Informationen, die Claims, enthält. Dabei gibt es einige vordefinierten Claims, die Sie verwenden können, oder Sie erstellen sich eigene Claims. Das einzige Claim welches wirklich benötigt, ist das Subject-Claim (sub), welches die User Id enthält.
Dabei werden die Claims in drei Kategorien unterteilt, registered, public und private.
Bei den registrierten Claim, registered, handelt es sich um die Claims, welche im Vorfeld für alle JWTs festgelegt wurde, um als standardmässig zu gelten.

  • iss → bei dem issuer handelt es sich um den Aussteller des JWts

  • aud → audience gibt Auskunft darüber, für wen dieser JWT ausgestellt wurde

  • exp → expiration definiert, wann der Token abläuft

  • nbf → not before beschliesst, aber welchem Zeitpunkt der Token gilt

  • iat → issued at time beschreibt, wann der Token erstellt wurde

  • jti → JWT ID

Zudem gibt es die Public Claims, hier können Entwickler verschieden Claims in der IANA JSON Web Token Claims registry selbst registrieren lassen. Dadurch kann man sich sicher sein, dass es keine doppelten Claims gibt. Und zum Schluss gibt es noch die privaten Claims, welche meistens spezifisch für die Anwendung genutzt werden.

{
  "sub": "1234567890",
  "name": "b-nova",
  "admin": true,
  "iat": 1516239022
}

Die Signatur

Die Signatur wird durch eine Base64-Kodierung des Headers und des Payloads, sowie der angegebenen Verschlüsselungsmethode innerhalb des Headers erstellt. Diese JSON Web Signature (JWS) ist ebenfalls ein RFC 7515 genormter Standard.

Die endgültige Signatur wird wie folgt erstellt:

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret)

Dabei werden die Teile des Tokens mit Punk getrennt, da der Punkt nicht innerhalb einer Base64-Kodierung vorkommt. In diesem Fall wird dieser Output generiert:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6ImItbm92YSIsImFkbWluIjp0cnVlLCJpYXQiOjE1MTYyMzkwMjJ9.
YDrKvpHj3s2B1Xm4SWQuFzs4fAkrUkDuyofBGdeQ27E

(Die Zeilenumbrüche, wurden nur aus Leserlichkeit hinzugefügt.)

Durch dieses Format ist es nun möglich den JSON Web Token über die URL weiterzugeben und zwischen Client und Server ausgetauscht werden.

JWS Compact Serialization vs. JWS JSON Serialization

Bei der JWS Compact Serialization handelt es sich um die klassische Variante eines signierten JWT. Denn diese ist so kompakte, dass der Token beispielsweise über die URL versendet werden kann. Dieser besteht aus den zuvor beschriebenen drei Teilen: Header, Payload, Signature. Somit kann hier ist nur eine Signatur über den Header und den Payload gelegt werden.

Bei JSON Serialization Variante wird der zuvor beschriebene Nachteil mit nur einer Signatur behoben. Denn hier mit ist es möglich, über denselben Payload und Header mehrere Signaturen zu legen. Durch diese Art hat man jedoch den Nachteil das dies nicht mehr URL-safe ist. Ebenfalls durch die verwendete Schreibweise ist dieses Format auch nicht mehr sehr kompakt.

Dabei ist der Aufbau in 2 Top-Level Teile, payload und signatures unterteilt. Der signatures Teil ist wiederum in drei weitere Teile protected, header und signature aufgeteilt. Bei dem payload-Teil handelt es sich um den komplett JWS-Payload mit einer Base64-Kodierung.

{
  "payload": "eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ",
  "signatures": [
    {
      "protected": "eyJhbGciOiJSUzI1NiJ9",
      "header": {
        "kid": "2010-12-29"
      },
      "signature": "cC4hiUPoj9Eetdgtv3hF80EGrhuB__dzERat0XF9g2VtQgr9PJbu3XOiZj5RZmh7AAuHIm4Bh-0Qc_lF5YKt_O8W2Fp5jujGbds9uJdbF9CUAr7t1dnZcAcQjbKBYNX4BAynRFdiuB--f_nZLgrnbyTyWzO5vRK5h6xBArLIARNPvkSjtQBMHlb1L07Qe7K0GarZRmB_eSN9383LcOLn6_dO--xi12jzDwusC-OkHWEsqtFZESc6BfI7noOPqvhJ1phCnvWh6IeYI2w9QOYEUipUTI8np6LbgGY9Fs98rqVt5AXLIhWkWywlVmtVrBp0igcN_IoypGlUPQGe77Rw"
    },
    {
      "protected": "eyJhbGciOiJFUzI1NiJ9",
      "header": {
        "kid": "e9bc097a-ce51-4036-9562-d2ade882db0d"
      },
      "signature": "DtEhU3ljbEg8L38VWAfUAqOyKAM6-Xx-F4GawxaepmXFCgfTjDxw5djxLa8ISlSApmWQxfKTUJqPP3-Kg6NU1Q"
    }
  ]
}

Das Attribut protected entspricht dabei dem Header innerhalb eines signatures-Objekt, ebenfalls in einer Base64-Kodierung.

Cookies VS. JWT

Der traditionelle Weg

Zunächst logt sich der User über den Browser mit seinen Credentials, wie der Mailadresse und dem Passwort ein. Dieses wird anschliessend mit einer POST-Methode an den Server gesendet. Dieser Server nimmt die Daten entgegen und überprüft, ob diese korrekt sind. Somit ist dieser Server für die Authentifizierung zuständig, ebenfalls muss dieser die aktuelle Session auf dem Server speichern und dem User eine entsprechende Session Id als Cookie zurücksenden. Macht der User nun einen weiteren Seitenaufruf muss dieser den entsprechenden Session Id Cookie mitsenden. Der Server muss anschliessend wieder hingehen und überprüfen, ob es eine Session mit der mitgesendeten Id gibt. Wenn dies der Fall ist, sendet der Server die entsprechende Antwort an den Client.

Der moderne Weg

Auch hier beginnt der Ablauf mit dem Login des Users indem er seine Credentials sendet. Doch hier kommt nun der Unterschied zu der vorherigen Variante, denn hier wird nichts auf dem Server gespeichert. Denn der Server erstellt hier nun eine signiertes JWT mit seinem eigenen Secret Key. Dieses JWT wird nun zurück an den Browser des Users gesendet. Wenn der User nun eine neue Anfrage an den Server sendet, muss dieser den JWT mitsenden. Der Server überprüft mittels des Secret Keys, ob der Token noch gültig ist oder nicht. Wenn dieser gültig ist, kann der Token deserialisiert werden und die entsprechenden User-Informationen liegen dem Server nun vor, ohne diese abgespeichert zu haben. Diese bietet nun den Vorteil, dass der JWT nun auf verschiedenen Server verwendet werde kann, wenn der Secret Key geteilt wird. Somit erspart sich der User einen weiteren Login und der zweite Server muss nicht noch eine zusätzliche Session speichern.

Vorteile von JWT

Sobald man JWTs verwendet, ist es nicht mehr nötig, eine Session auf dem Server zu speichern, somit lässt sich eine Menge an Speicherkapazitäten sparen. Denn anstatt diese zu speichern, werden die notwendigen Informationen bei jedem Request wieder mitgeliefert und der Token wird neu verifiziert. Der Server muss dabei nur den Secret Key speichern, um den Token auf seine Gültigkeit zu überprüfen oder um neue Tokens zu erstellen.

Da es für alle gängigen Programmiersprachen bereits Implementierungen gibt, können JWTs in der eigenen Anwendungen schnell verwendet werden. Zu diesen Sprachen gehören beispielsweise Go, Java, Rust, JavaScript oder Python.

Wie Sie bereits anhand des Aufbaus sehen konnten, handelt es sich hier um eine sehr minimal Menge an notwendigen Inhalten, um eine sehr zuverlässige Authentifizierung zu ermöglichen. Durch die Reduzierung des gesamten Netzwerk-Overhead kann hier deutlich an Performance gewonnen werden.

Dadurch das es die Möglichkeit gibt, eigene/ private Claims zu erstellen, ist JWT sehr vielseitig einsetzbar und ist nicht auf bestimmte Einsatzgebiete festgelegt.

JWT Transport

Obwohl es sich bei JSON WebTokens um einen definierten Standard handelt, wird nicht definiert, wie ein JWT übertragen werden soll. Bei in den meisten Fällen wir dies über HTTP mittels des Authorization Bearer header gemacht. Dasselbe Prinzip wird bei OAuth 2.0 ebenfalls verwendet. Sobald man von einem bearer Token redet, bedeutet dies, dass der Besitzer diesen Token verwenden kann, ohne seinen Besitz zu beweisen. Deshalb sollte die Übertragung immer verschlüsselt vollzogen werden.

Aufbau eines JWE

Auch hier handelt es sich wieder um einen Token, welcher wieder durch Punkte getrennt wird, jedoch aus fünf Teilen besteht. Da es sich hier um ein etwas umfassenderes Thema handelt, werden wir und nur in kürze den Aufbau anschauen.

JOSE header

Dieser ist gleich aufgebaut wie zuvor der Inhalt eines JWS Header. Jedoch gibt es nun hier noch zwei neue Elemente, die hinzugefügt werden können. Das erste Element ist enc und legt den content encryption algorithm fest. Das zweite Element anc definiert den Verschlüsselungs-Algorithmus für den Content Encryption Key (CEK).

JWE Encrypted Key

Der Inhalt dieses Feldes wird mit dem Key des Empfängers verschlüsselt.

JWE initialization vector

Dabei handelt es sich um eine zufällig erstellte Zahl, welche mit dem Secret Key verwendet wird, um die Daten zu verschlüsseln.

JWE Ciphertext

Dies ist der eigentlich verschlüsselte Payload. Dieser wird erstellt mittels des Content Encryption Key (CEK), JWE initialization vector und dem Additional Authentication Data (AAD) Wert. Der Verschlüsselung Algorithmus ist dabei durch den im Header Definierten enc festgelegt.

JWE Authentication Tag

Für eine Integritätsprüfung wird das Authentication Tag verwendet.

Fazit

Da JWT mittlerweile sehr verbreitet ist und eine bevorzugte Variante gegenüber der traditionellen Session ID Cookies bietet, sollte man sich auf jeden Fall mit diesem Thema auseinandersetzten. Da aber JWT durch die unterschiedlichen Varianten doch sehr vielseitig sein kann, ist es wichtig, die Unterschiede zu kennen.

Ricky Elfner – Denker, Überlebenskünstler, Gadget-Sammler. Dabei ist er immer auf der Suche nach neuen Innovationen, sowie Tech News, um immer über aktuelle Themen schreiben zu können.