it-swarm.com.de

JWT (JSON Web Token) automatische Verlängerung des Ablaufs

Ich möchte die JWT-basierte Authentifizierung für unsere neue REST - API implementieren. Kann der Ablauf jedoch automatisch verlängert werden, da der Ablauf im Token festgelegt ist? Ich möchte nicht, dass sich Benutzer nach jeweils X Minuten anmelden müssen, wenn sie die Anwendung in diesem Zeitraum aktiv verwendet haben. Das wäre ein riesiger UX-Fehler. 

Durch die Verlängerung des Ablaufs wird jedoch ein neues Token erstellt (das alte ist bis zu seinem Ablauf noch gültig). Und nach jeder Anfrage ein neues Token zu generieren, erscheint mir dumm. Klingt nach einem Sicherheitsproblem, wenn mehrere Token gleichzeitig gültig sind. Natürlich könnte ich die alte benutzte Liste mit einer schwarzen Liste für ungültig erklären, aber ich würde die Token speichern müssen. Und einer der Vorteile von JWT ist kein Speicher.

Ich habe gefunden, wie Auth0 es gelöst hat. Sie verwenden nicht nur ein JWT-Token, sondern auch ein Aktualisierungstoken: https://docs.auth0.com/refresh-token

Um dies jedoch (ohne Auth0) zu implementieren, muss ich Aktualisierungsmarker speichern und deren Ablauf beibehalten. Was ist dann der wirkliche Nutzen? Warum nicht nur ein Token (kein JWT) haben und den Ablauf auf dem Server behalten?

Gibt es noch andere Möglichkeiten? Ist die Verwendung von JWT für dieses Szenario nicht geeignet?

410
maryo

Ich arbeite bei Auth0 und war am Design des Refresh-Token-Features beteiligt.

Es hängt alles von der Art der Anwendung ab und hier ist unser empfohlener Ansatz.

Web Applikationen

Ein gutes Muster ist das Aktualisieren des Tokens, bevor es abläuft.

Legen Sie den Ablauf des Token auf eine Woche fest und aktualisieren Sie das Token jedes Mal, wenn der Benutzer die Webanwendung öffnet, und jede Stunde. Wenn ein Benutzer die Anwendung für mehr als eine Woche nicht öffnet, muss er sich erneut anmelden. Dies ist eine akzeptable Webanwendung UX.

Um das Token zu aktualisieren, benötigt Ihre API einen neuen Endpunkt, der eine gültige, nicht abgelaufene JWT empfängt und dieselbe signierte JWT mit dem neuen Ablauffeld zurückgibt. Dann speichert die Webanwendung das Token irgendwo.

Mobile/native Anwendungen

Die meisten nativen Anwendungen melden sich einmalig und einmalig an. 

Die Idee ist, dass das Aktualisierungstoken nie abläuft und immer gegen eine gültige JWT ausgetauscht werden kann.

Das Problem mit einem Token, das niemals abläuft, ist, dass never niemals bedeutet. Was machen Sie, wenn Sie Ihr Telefon verlieren? Es muss also vom Benutzer auf irgendeine Weise identifiziert werden können, und die Anwendung muss eine Möglichkeit bieten, den Zugriff zu sperren. Wir haben uns entschieden, den Namen des Geräts zu verwenden, z. "maryos iPad". Dann kann der Benutzer zur Anwendung gehen und den Zugriff auf "maryos iPad" widerrufen.

Ein anderer Ansatz besteht darin, das Aktualisierungstoken für bestimmte Ereignisse zu widerrufen. Ein interessantes Ereignis ist das Ändern des Passworts.

Wir glauben, dass JWT für diese Anwendungsfälle nicht nützlich ist. Daher verwenden wir eine zufällig generierte Zeichenfolge und speichern diese auf unserer Seite.

478

In dem Fall, in dem Sie mit dem Auth selbst umgehen (d. H. Keinen Provider wie Auth0 verwenden), kann Folgendes funktionieren:

  1. Ausgabe eines JWT-Tokens mit relativ kurzer Laufzeit, beispielsweise 15 Minuten.
  2. Die Anwendung prüft das Verfallsdatum des Tokens vor einer Transaktion, für die ein Token erforderlich ist (das Token enthält ein Verfallsdatum). Wenn das Token abgelaufen ist, fordert es die API zuerst auf, das Token zu "aktualisieren" (dies erfolgt transparent für die UX).
  3. Die API ruft eine Tokenaktualisierungsanforderung ab, prüft jedoch zunächst die Benutzerdatenbank, um festzustellen, ob für dieses Benutzerprofil ein Flag 'reauth' gesetzt wurde (das Token kann eine Benutzer-ID enthalten). Wenn das Flag vorhanden ist, wird die Tokenaktualisierung abgelehnt, andernfalls wird ein neues Token ausgegeben.
  4. Wiederholen.

Das 'reauth'-Flag im Datenbank-Backend wird gesetzt, wenn der Benutzer beispielsweise sein Kennwort zurückgesetzt hat. Das Flag wird entfernt, wenn sich der Benutzer das nächste Mal anmeldet.

Angenommen, Sie haben eine Richtlinie, nach der sich ein Benutzer mindestens alle 72 Stunden anmelden muss. In diesem Fall würde Ihre API-Token-Aktualisierungslogik auch das letzte Anmeldedatum des Benutzers aus der Benutzerdatenbank prüfen und die Token-Aktualisierung auf dieser Grundlage verweigern/zulassen.

58
IanB

Eine alternative Lösung zum Ungültigmachen von JWTs ohne zusätzlichen sicheren Speicher im Backend ist die Implementierung einer neuen Ganzzahlspalte jwt_version in der Benutzertabelle. Wenn der Benutzer vorhandene Token abmelden oder auslaufen möchte, wird das Feld jwt_version einfach inkrementiert.

Kodieren Sie beim Generieren einer neuen JWT den jwt_version in die JWT-Nutzdaten. Erhöhen Sie den Wert gegebenenfalls zuvor, wenn die neue JWT alle anderen ersetzen soll.

Bei der Validierung der JWT wird das jwt_version-Feld mit dem user_id verglichen und die Berechtigung wird nur erteilt, wenn sie übereinstimmt.

11
Ollie Bennett

Ich habe herumgespielt, als ich unsere Anwendungen mit RESTful Apis im Backend auf HTML5 umstellte. Die Lösung, die ich fand, war:

  1. Der Client erhält bei erfolgreicher Anmeldung ein Token mit einer Sitzungszeit von 30 Minuten (oder wie auch immer die übliche serverseitige Sitzungszeit).
  2. Ein clientseitiger Zeitgeber wird erstellt, um einen Dienst aufzurufen, um das Token vor Ablauf der Zeit zu erneuern. Das neue Token wird die bestehenden Aufrufe in Zukunft ersetzen.

Wie Sie sehen, verringert dies die häufigen Tokenanforderungen für die Aktualisierung. Wenn der Benutzer den Browser/die Anwendung schließt, bevor der erneute Token-Aufruf ausgelöst wird, läuft das vorherige Token rechtzeitig ab und der Benutzer muss sich erneut anmelden.

Eine kompliziertere Strategie kann implementiert werden, um auf Benutzerinaktivität zu reagieren (z. B. eine geöffnete Browser-Registerkarte vernachlässigt). In diesem Fall sollte der Aufruf für das erneuerte Token die erwartete Ablaufzeit enthalten, die die definierte Sitzungszeit nicht überschreiten sollte. Die Anwendung muss die letzte Benutzerinteraktion entsprechend verfolgen.

Ich mag die Vorstellung eines langen Ablaufs nicht, daher funktioniert dieser Ansatz möglicherweise nicht gut mit nativen Anwendungen, die weniger häufige Authentifizierung erfordern.

11
coolersport

Gute Frage - und die Frage selbst ist reich an Informationen.

Der Artikel Refresh-Token: Wann sie zu verwenden sind und wie sie mit JWTs interagieren gibt eine gute Idee für dieses Szenario. Einige Punkte sind: -

  • Aktualisierungs-Token enthalten die erforderlichen Informationen, um einen neuen Zugriff zu erhalten Token. 
  • Refresh-Token können ebenfalls ablaufen, sind aber ziemlich langlebig. 
  • Refresh-Token unterliegen in der Regel strengen Speicheranforderungen, um sicherzustellen, dass sie nicht auslaufen. 
  • Sie können auch vom Berechtigungsserver auf die schwarze Liste gesetzt werden.

Werfen Sie auch einen Blick auf auth0/angle-jwt anglejs

Für Web-API. read Aktivieren Sie OAuth-Aktualisierungsmarker in AngularJS-App mithilfe von ASP .NET-Web-API 2 und Owin

9
Lijo

jwt-autorefresh

Wenn Sie einen Knoten (React/Redux/Universal JS) verwenden, können Sie npm i -S jwt-autorefresh installieren.

Diese Bibliothek plant die Aktualisierung von JWT-Token mit einer vom Benutzer berechneten Anzahl von Sekunden, bevor das Zugriffstoken abläuft (basierend auf dem im Token codierten exp anspruch). Es verfügt über eine umfangreiche Testsuite und prüft, ob eine Reihe von Bedingungen vorhanden ist, um sicherzustellen, dass bei einer ungewöhnlichen Aktivität eine beschreibende Meldung zu Fehlkonfigurationen in Ihrer Umgebung angezeigt wird.

Vollständige Beispielimplementierung

import autorefresh from 'jwt-autorefresh'

/** Events in your app that are triggered when your user becomes authorized or deauthorized. */
import { onAuthorize, onDeauthorize } from './events'

/** Your refresh token mechanism, returning a promise that resolves to the new access tokenFunction (library does not care about your method of persisting tokens) */
const refresh = () => {
  const init =  { method: 'POST'
                , headers: { 'Content-Type': `application/x-www-form-urlencoded` }
                , body: `refresh_token=${localStorage.refresh_token}&grant_type=refresh_token`
                }
  return fetch('/oauth/token', init)
    .then(res => res.json())
    .then(({ token_type, access_token, expires_in, refresh_token }) => {
      localStorage.access_token = access_token
      localStorage.refresh_token = refresh_token
      return access_token
    })
}

/** You supply a leadSeconds number or function that generates a number of seconds that the refresh should occur prior to the access token expiring */
const leadSeconds = () => {
  /** Generate random additional seconds (up to 30 in this case) to append to the lead time to ensure multiple clients dont schedule simultaneous refresh */
  const jitter = Math.floor(Math.random() * 30)

  /** Schedule autorefresh to occur 60 to 90 seconds prior to token expiration */
  return 60 + jitter
}

let start = autorefresh({ refresh, leadSeconds })
let cancel = () => {}
onAuthorize(access_token => {
  cancel()
  cancel = start(access_token)
})

onDeauthorize(() => cancel())

disclaimer: Ich bin der Betreuer

6
cchamberlain

Ich habe dies tatsächlich in PHP implementiert, indem ich den Guzzle-Client verwendete, um eine Client-Bibliothek für die API zu erstellen, aber das Konzept sollte für andere Plattformen funktionieren.

Grundsätzlich gebe ich zwei Token aus, einen kurzen (5 Minuten) und einen langen, der nach einer Woche abläuft. Die Clientbibliothek verwendet Middleware, um eine Aktualisierung des Kurztokens zu versuchen, wenn sie eine 401-Antwort auf eine Anforderung erhält. Die ursprüngliche Anforderung wird dann erneut versucht, und falls sie aktualisiert werden konnte, wird die richtige Antwort transparent für den Benutzer angezeigt. Wenn dies fehlgeschlagen ist, wird der 401 nur an den Benutzer gesendet.

Wenn das kurze Token abgelaufen ist, aber immer noch authentisch ist und das lange Token gültig und authentisch ist, wird das kurze Token mithilfe eines speziellen Endpunkts für den Dienst aktualisiert, für den das lange Token authentifiziert wird (dies ist das einzige, für das es verwendet werden kann). Das kurze Token wird dann verwendet, um ein neues langes Token zu erhalten, wodurch es jedes Mal um eine Woche verlängert wird, wenn es das kurze Token aktualisiert.

Dieser Ansatz ermöglicht es uns auch, den Zugriff innerhalb von maximal 5 Minuten zu widerrufen, was für unsere Verwendung akzeptabel ist, ohne dass eine schwarze Liste von Token gespeichert werden muss.

Späte Bearbeitung: Wenn Sie diese Monate nach dem Neustart in meinem Kopf noch einmal lesen, sollte ich darauf hinweisen, dass Sie den Zugriff beim Aktualisieren des kurzen Token widerrufen können, da dadurch die Möglichkeit teurerer Anrufe besteht (z. B. Aufruf der Datenbank, um zu sehen, ob der Benutzer nachschaut wurde verboten), ohne dafür bei jedem Anruf Ihres Dienstes zu zahlen.

6
BytePorter

Wie wäre es mit diesem Ansatz:

  • Bei jeder Clientanforderung vergleicht der Server die Ablaufzeit des Tokens mit (currentTime - lastAccessTime).
  • Wenn expirationTime <(currentTime - lastAccessedTime) ist, wird die letzte lastAccessedTime in currentTime geändert.
  • Bei Inaktivität des Browsers für eine Zeitspanne, die über die Ablaufzeit hinausgeht, oder falls das Browserfenster geschlossen wurde und die Ablaufzeit> (currentTime - lastAccessedTime) , kann der Server das Token verfallen lassen und den Benutzer erneut anmelden .

Wir benötigen in diesem Fall keinen zusätzlichen Endpunkt zum Aktualisieren des Tokens. Würde jedes Feedack begrüßen.

2
sjaiswal

Ich habe dieses Problem durch Hinzufügen einer Variablen in den Token-Daten gelöst:

softexp - I set this to 5 mins (300 seconds)

Ich setze die Option expiresIn auf die gewünschte Zeit, bevor der Benutzer sich erneut anmelden muss. Meins ist auf 30 Minuten eingestellt. Dieser muss größer als der Wert von softexp sein.

Wenn meine clientseitige App eine Anforderung an die Server-API sendet (wo ein Token erforderlich ist, z. B. die Seite mit der Kundenseite), überprüft der Server, ob das übergebene Token noch gültig ist oder nicht, und zwar basierend auf seinem ursprünglichen Verfallsdatum (expiresIn). Wenn es nicht gültig ist, antwortet der Server mit einem bestimmten Status für diesen Fehler, z. INVALID_TOKEN.

Wenn das Token basierend auf dem Wert expiredIn noch gültig ist, den Wert softexp jedoch bereits überschritten hat, antwortet der Server für diesen Fehler mit einem separaten Status, z. EXPIRED_TOKEN:

(Math.floor(Date.now() / 1000) > decoded.softexp)

Wenn auf dem Client eine EXPIRED_TOKEN-Antwort empfangen wurde, sollte das Token automatisch erneuert werden, indem eine Erneuerungsanforderung an den Server gesendet wird. Dies ist für den Benutzer transparent und wird automatisch für die Client-App übernommen.

Die Erneuerungsmethode auf dem Server muss prüfen, ob das Token noch gültig ist:

jwt.verify(token, secret, (err, decoded) => {})

Der Server weigert sich, Token zu erneuern, wenn die obige Methode fehlgeschlagen ist.

2
James A

Gehen Sie wie folgt vor, um Ihr JWT-Zugriffstoken zu widerrufen: 

1) Wenn Sie sich anmelden, senden Sie als Antwort an den Client 2 Token (Access-Token, Refresh-Token).
2) Das Zugriffstoken hat weniger Verfallszeit und die Aktualisierung hat eine lange Verfallszeit.
3) Der Client (Front-End) speichert das Aktualisierungstoken in seinem lokalen Speicher und das Zugriffstoken in Cookies.
4) Der Client verwendet das Zugriffstoken zum Aufrufen von apis. Wenn es jedoch abläuft, wählen Sie das Aktualisierungs-Token aus dem lokalen Speicher aus, und rufen Sie den Authentifizierungsserver auf, um das neue Token abzurufen.
5) Auf Ihrem Auth-Server wird ein API angezeigt, das ein Aktualisierungstoken akzeptiert, auf Gültigkeit prüft und ein neues Zugriffstoken zurückgibt.
6) Sobald das Aktualisierungstoken abgelaufen ist, wird der Benutzer abgemeldet. 

Bitte lassen Sie mich wissen, wenn Sie weitere Informationen benötigen, ich kann den Code (Java + Spring Boot) auch teilen.

0
Bhupinder Singh