sainth.de

sainth.de

Ein persönlicher Blog über alles, was mich interessiert, also überwiegend Programmierung im Allgemeinen und Spieleprogrammierung im Speziellen.

JWTs, eine Ergänzung zu BasicAuth

Wie man JWTs in Kombination mit BasicAuth einsetzen kann.

Tobias Wink

Lesezeit: 5 Minuten

JWT logo

Die meisten REST-APIs unterstützen BasicAuth, wenn sie eine Authentifizierung erfordern und die Nutzerdaten selber verwalten. Bei der Wahl der Funktion zum Schutz von gespeicherten Passwörtern gilt es, die richtige Balance zwischen Nutzerkomfort und Angreiferschutz zu finden. Man möchte einerseits Angreifer möglichst lange aufhalten, andererseits aber auch Benutzern möglichst geringe Antwortzeiten ermöglichen. Alle von der OWASP empfohlenen adaptiven Passwort-Hash-Verfahren bieten daher die Möglichkeit, die Geschwindigkeit des Algorithmus über einen Work Factor zu konfigurieren. Um diesen Work Factor möglichst hoch wählen zu können, ohne dabei die Benutzer unnötig zu verärgern, bietet sich nun die Nutzung von JWTs an.

Was ist ein JWT?

Ein JSON Web Token, kurz JWT (ausgesprochen “jot”), ist ein einfaches und kompaktes Format zur Übertragung von Claims. JWTs sind darauf ausgelegt, in Bereichen eingesetzt zu werden, die sensibel auf Leerzeichen reagieren, wie beispielweise HTTP Authorization Header. Ein JWT ist eigentlich nur ein JSON-Objekt, welches die Claims als Inhalt hat. Im zugehörigen RFC 7519 werden einige Claims vordefiniert, meiner Meinung nach sind davon die Wichtigsten:

  • sub-Claim (Subject): Enthält den Prinzipal für den JWT.
  • exp-Claim (Expiration Date): Definiert das Ablaufdatum für den JWT. Nach Ablauf dieses Timestamps darf der JWT nicht mehr akzeptiert oder verarbeitet werden.
  • jti-Claim (JWT ID): Stellt eine Unique-ID für den JWT dar. Mit Hilfe dieser Claim sollte es möglich sein, JWTs trotz ähnlicher sonstiger Inhalte zu unterscheiden und dadurch Replay-Attacken zu verhindern.

Sollten die vordefinierten Claims nicht ausreichen, darf man sie um private Claims erweitern, solange es keine Konflikte zu den öffentlichen Claims gibt.

Wie verwendet man sie nun?

JWTs werden als Payload für JWS-Elemente oder auch als Plaintext innerhalb von JWE-Elementen verwendet, wobei ich mich hier auf JWS-Elemente beschränken werde. Ein JWS-Element besteht aus den drei Bereichen Header, Payload und Signatur, die jeweils durch einen Punkt “.” getrennt werden. Nachfolgend ein Beispiel für einen fertig enkodierten JWT - für bessere Lesbarkeit wurden die Bereiche in eigene Zeilen verschoben:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwicm9sZSI6ImFkbWluIn0.
OC16ViAXu0_JaKXlUKQVHCKeZn1tXTwcwMDRpmY6xyc

Header und Payload sind <abbr title=“Abgewandelte Version von Base64, bei der “+” und “/” durch “-” bzw. “” ersetzt werden.">Base64-URL enkodierte JSON-Objekte. Die Signatur wird über die mit “.” konkatenierten und bereits enkodierten Werte von Header und Payload erstellt und ist ebenfalls <abbr title=“Abgewandelte Version von Base64, bei der “+” und “/” durch “-” bzw. “” ersetzt werden.">Base64-URL enkodiert. Der für die Signatur verwendete Algorithmus ist in der Header Claim alg festgelegt. Ein mögliches Pseudocode-Beispiel der Signaturerstellung für das vorige Beispiel wäre:

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

Laut RFC 7515 müssen JWS-Implementierungen nur einen einzigen Algorithmus für die Signatur unterstützen: HMAC mit SHA-256, auch HS256 genannt. Aber es wird empfohlen, RS256 (RSASSA PKCS1 v1.5 mit SHA-256) und ES256 (ECDSA mit P-256 und SHA-256) ebenfalls zu unterstützen. Viele Bibliotheken unterstützen auch entsprechende Abwandlungen der drei Algorithmen mit SHA-384 oder SHA-512 statt SHA-256.

Alle Signaturverfahren erfüllen denselben Zweck: Sie ermöglichen die Sicherstellung der Authentizität der JWT-Claims. Alle HMAC-Verfahren nutzen ein Shared-Secret für die Generierung/Validierung, aus diesem Grund muss das Vertrauen zwischen Parteien, die JWS mit HMAC-Verfahren nutzen, sehr groß sein. Falls das Vertrauen zwischen den Parteien nicht so groß ist (bspw. unterschiedliche Betreiber), sollte unbedingt ein Verfahren verwendet werden, welches auf RSASSA oder ECDSA basiert, da beide Verfahren Public-Key Algorithmen sind. Somit wird der JWT vom Aussteller bei der Erzeugung mit dem Private-Key signiert und jeder, der den zugehörigen Public-Key hat, kann die Signatur anschließend validieren.

Wie verwendet man JWS nun in Verbindung mit BasicAuth?

BasicAuth sollte nur für den ersten Request verwendet werden. In jeder Antwort sollte ein neuer Token mitgeschickt bzw. im Cookie gespeichert werden, bestenfalls mit neuem Wert für jti-Claim (welcher natürlich serverseitig auch ausgewertet werden sollte). Der Weg über den Cookie ist meiner Meinung nach zu bevorzugen, falls es möglich ist, da man frontendseitig nicht daran denken muss, den Token auszulesen um ihn beim nächsten Request wieder mitzuschicken. Außerdem erhöht es die Sicherheit, wenn der Cookie das HttpOnly-Flag hat, da man per JavaScript nun nicht mehr darauf zugreifen kann. Sollte man den Token dennoch bei jedem Request mitsenden wollen/ müssen, so hat es sich etabliert, diesen als Bearer-Token zu senden, also dann beispielweise Authorization : Bearer <JWS>.

Da so das Passwort nur noch beim ersten Request mitgesendet und somit überprüft werden muss, kann man nun das verwendete Passwort-Hash-Verfahren so einstellen, dass es mindestens 200ms für das Hashen benötigt. Dadurch wird auch die Passwortsicherheit erhöht.

Als weiteres Extra kann man nun auch schon im Reverse-Proxy bzw. Loadbalancer feststellen, ob ein Client Zugang hat, indem der Token dort bereits überprüft wird. Dafür gibt es freie Module bzw. hat NGINX Plus ab R10 native Unterstützung für JWTs. Dadurch wird einerseits das Backend entlastet und andererseits können auch Komponenten geschützt werden, die nichts über Authentifizierung wissen bspw. ein CDN.

Worauf man achten sollte

  • Diese Tokens sollten nur der Authentifizierung dienen und nicht als Session-Cookies missbraucht werden.
  • JWT-Claims als Payload von JWS-Elementen sind für jeden lesbar, somit sollten nur solche Informationen dort gespeichert/ übertragen werden, die auch für jeden sichtbar sein dürfen.
  • JWS-Elemente ohne Signatur sollten als ungültig angesehen werden.
  • Die exp-Claim sollte immer verwendet werden, um die Gültigkeit eines Tokens einzuschränken.
  • Die jti-Claim sollte immer verwendet werden, um Replay-Attacken vorzubeugen.
  • Wenn man JWTs zur Authentifizierung von REST-APIs verwendet, sollte der JWT in einem Cookie mit gesetztem HttpOnly gespeichert werden.
  • Wenn Frontend und REST-Backend über dieselbe Domain erreichbar sind, sollte der Cookie SameSite nutzen, auch wenn es bislang nur durch Chromium/ Chrome und Opera unterstützt wird.
  • RFC 7515: JSON Web Signature (JWS)
  • RFC 7516: JSON Web Encryption (JWE)
  • RFC 7517: JSON Web Key (JWK)
  • RFC 7518: JSON Web Algorithms (JWA)
  • RFC 7519: JSON Web Token (JWT)
  • RFC 7520: Examples of Protecting Content Using JSON Object Signing and Encryption (JOSE)
  • jwt.io: Website über JWTs von auth0 mit einem kostenlosen, ausführlichen Handbuch über JWTs.

Neueste Artikel

Kategorien

Über Mich

Ich bin nur ein einfacher Mensch, der sich etwas mit Computern beschäftigt. 😉