Erklärung zu Keycloak-Refresh-Tokens und Sitzungszeitlimits
Warum Keycloak-Refresh-Anfragen 400 zurückgeben, wie sich Idle- und Max-Session-Timeouts unterscheiden, und wie man Refresh-Tokens mit rotationssicherer Speicherung in JavaScript verwendet.
Quellen: Refresh Tokens and Session Timeouts in Keycloak: Why a 400 Isn’t Always a Bug
Apps, die auf Keycloak basieren, erhalten gelegentlich beim Erneuern eine Antwort des Token-Endpunkts mit 400 Bad Request. Kurz gesagt bedeutet das, dass die Erneuerung fehlschlägt. Häufig weigert sich Keycloak jedoch absichtlich: Die Sitzung ist abgelaufen, die maximale Gültigkeitsdauer wurde erreicht, sie wurde widerrufen, oder das Erneuerungstoken ist nicht mehr gültig. Solche Fälle sind Teil der Richtlinien und keine Defekte.
Dieser Leitfaden erläutert die wichtigsten Aspekte in der Produktion: Welche Funktionen Zugriffs- und Erneuerungstoken haben, wie die Erneuerung von Anfang bis Ende abläuft, welche beiden Sitzungstimer eine erfolgreiche Erneuerung ermöglichen, sowie die gängigen Vorgehensweisen – und Fehlermuster – beim Umgang mit dem Rest.
Warum dieser Artikel existiert
Wenn der Token-Endpunkt 400 zurückgibt, melden Teams oft einen Authentifizierungsfehler. Die erste wichtige Frage ist, welche Gültigkeits- oder Widerrufskonvention in Kraft getreten ist. Das Verständnis dieser Antwort verhindert, dass eine korrekte Ablehnung als Ausfall interpretiert wird.
1. Zwei Token, zwei völlig unterschiedliche Aufgaben
Nach dem Anmelden gibt Keycloak in der Regel ein Zugriffstoken, ein Auffrischungstoken und ein ID-Token aus. Die tägliche Arbeit hängt von den ersten beiden ab.
- Zugriffstoken – Wird bei normalen API-Aufrufen mitgeliefert, um zu bestätigen, wer aufruft. Die Gültigkeitsdauer ist kurz, meist in Minuten angegeben.
- Auffrischungstoken – Wird nur an Keycloak gesendet, um ein neues Zugriffstoken zu erstellen. Die Gültigkeitsdauer ist länger, in der Regel dreißig Minuten oder mehr.
- Offline-Token – Optionaler, langanhaltender Zugriff, der länger als eine normale SSO-Sitzung andauern kann und durch eigene Richtlinien geregelt wird.
Eine praktische Vorstellung: Das Zugriffstoken ist ein temporäres Ausweisdokument, das bei jeder „Tür“ vorgezeigt wird. Das Auffrischungstoken ist das Dokument, das den Ausweis ersetzt; es wird niemals an Anwendungs-APIs gezeigt und muss ausschließlich zum Token-Endpunkt von Keycloak gesendet werden.
2. Der normale Ablauf
Das Anmelden gibt den Token-Set zurück. Danach fügt der Client bei jedem Aufruf den Zugriffstoken hinzu:
GET /api/products
Authorization: Bearer eyJ...
Die Gültigkeitsdauer der Zugriffstoken ist kurz – fünf bis fünfzehn Minuten sind üblich. Wenn sich der Benutzer um 09:00 anmeldet und ein fünfminütiger Zugriffstoken vergeben wird, liefert der nächste API-Aufruf um 09:05 den Wert 401 Unauthorized.
Dieser Fehler 401 bedeutet keine sofortige Anmeldungssperre. Es handelt sich dabei um ein Signal zum Erneuern des Tokens.
3. Der Erneuerungsaufruf selbst
Der Client sendet den Erneuerungstoken an Keycloak’s Token-Endpunkt:
POST /realms/{realm}/protocol/openid-connect/token
Content-Type: application/x-www-form-urlencoded
grant_type=refresh_token
client_id=<client-id>
refresh_token=<refresh-token>
Ein korrekt funktionierender Ablauf sieht wie folgt aus:
{
"access_token": "new-access-token",
"expires_in": 300,
"refresh_token": "new-refresh-token",
"refresh_expires_in": 1800
}
Der Client speichert den neuen Zugriffstoken, versucht den ursprünglichen Anfragevorgang erneut – der Benutzer bemerkt diesen Vorgang nicht.
4. Detaillierungen zur Rotation: Die gespeicherten Erneuerungskennwerte müssen jedes Mal ersetzt werden
Die Antwort enthält oft auch ein neues Refresh-Token. Wenn die Refresh-Token-Rotation aktiviert ist, wird das vorherige Refresh-Token umgehend ungültig, sobald ein neues ausgestellt wird. Die Wiederverwendung des alten Wertes führt beim nächsten Erneuern zum Scheitern.
// Correct
saveAccessToken(response.access_token);
if (response.refresh_token) {
saveRefreshToken(response.refresh_token);
}
// Wrong — keeps using the token that rotation just invalidated
const refreshToken = originalRefreshToken;
Durch das Speichern des rotierten Refresh-Wertes bei jedem Erfolg – und nicht nur nach dem Anmelden – unterscheidet sich eine Sitzung, die stundenlang reibungslos erneuert wird, von einer, die bereits beim zweiten Versuch fehlschlägt.
5. Idle versus Max: Die Uhren, die das Erneuern steuern
Realm-Installationen liefern mindestens zwei Sitzungsuhren, und das Vertauschen ihrer Bedeutungen verursacht die meisten Anfragen vom Typ „Warum ist die Authentifizierung fehlgeschlagen?“:
- SSO Session Idle – Wie lange die Sitzung ohne Aktivität bestehen darf, bevor Keycloak sie beendet. Ein erfolgreicher Refresh zählt als Aktivität und setzt diese Uhr zurück.
Kurz gesagt:
IDLE = how long since the session was last active
MAX = how long since the user originally logged in
Mit Idle = 30 Minuten und Max = 16 Stunden halten häufige Neuladungen die Inaktivitätsuhr den ganzen Tag über jung, doch nach sechzehn Stunden seit dem Anmelden endet die Sitzung, selbst wenn der Benutzer nur eine Minute zuvor aktiv war. Eine perfekte Neuladetaktung kann die maximale Zeit nicht überschreiten; sie bewahrt lediglich die Gültigkeit innerhalb dieses Zeitraums.
Die Einstellungen Idle/Max pro Client spiegeln die Zeiten des Realm wider, aber nur für diesen Client. Gilt die strengere der beiden Grenzen.
Warum in den Beispielen eine maximale Zeit von sechzehn Stunden angegeben wird: Ein ERP-Team mit einer Schicht von etwa zwölf Stunden könnte zwölf Arbeitsstunden plus eine vierstündige Pufferzeit wählen, damit eine Sitzung einen vollen Tag übersteht, ohne dass ein erneutes Anmelden mitten in der Arbeit erforderlich ist – und ohne ewig anzudauern. Die Zahl ist eine unternehmerische Entscheidung; die wichtige Fähigkeit besteht darin zu wissen, welchen Zeitrahmen sie beeinflusst.
Warum eine dreißigminütige Inaktivität immer noch überrascht: Selbst bei einer großzügigen Obergrenze kann ein langer Mittagsschlaf oder eine lange Besprechung die Inaktivitätszeit früh beenden. Wenn Benutzer ohne erneute Authentifizierung zurückkehren sollen, sollte die Handhabung der Inaktivität als Produkt- oder Sicherheitsentscheidung betrachtet werden – und nicht nur im Client-Code abgedeckt werden.
6. Lesen von 400 vom Token-Endpoint, ohne von einem Ausfall auszugehen
Ein fehlgeschlagener Refresh kann wie folgt aussehen:
{
"error": "invalid_grant",
"error_description": "Token is not active"
}
Dieser Payload kann auf mehrere verschiedene Fälle hinweisen:
- Die Refresh-Zertifikate sind von selbst abgelaufen
Niemand dieser Fälle bedeutet „die API ist down“. Prüfen Sie zuerst die Werte error und error_description, bevor Sie etwas anderes annehmen.
7. Korrekte Handhabung im Frontend
Ein zuverlässiges Muster:
401 on API request
→ try refresh, exactly once
→ success: save new tokens, retry the original request
→ failure: clear auth state, redirect to login
Vermeiden Sie endlose Versuche: 401, Erneuerung, 400, erneute Erneuerung. Eine ungültige Sitzung führt sonst zu einer Flut an Token-Anfragen sowie einem dauerhaft laufenden Ladeindikator, der sich nicht beheben lässt. Betrachten Sie einen Erneuerungsfehler als endgültig – löschen Sie die Anmeldeinformationen, reinigen Sie den Authenzustand, leiten Sie auf die Anmeldeseite um und beenden Sie die Aktion.
8. Fan-out 401s erfordern eine einzige gemeinsame Verlängerung
Ein Dashboard, das mit einem abgelaufenen Zugriffstoken gleichzeitig fünf Anfragen sendet, erhält fünf 401-Fehler. Wenn jeder Pfad unabhängig voneinander aktualisiert wird, macht der erste Erfolg das Token ungültig, das die anderen vier noch besitzen – sodass vier Verlängerungsversuche mit 400 invalid_grant fehlschlagen, obwohl die Sitzung ursprünglich in Ordnung war.
Serialisieren der Verlängerung: Der erste 401-Fehler öffnet eine einzige Promise; die anderen Anfragen warten auf dieselbe Promise und versuchen es erneut, sobald sie abgeschlossen ist.
if (tokenExpired) {
if (!refreshInProgress) {
refreshInProgress = refreshToken();
}
try {
const tokens = await refreshInProgress;
updateTokens(tokens);
return retryOriginalRequest();
} finally {
refreshInProgress = null;
}
}
Egal ob es sich um eine manuell erstellte Promise, eine Axios-Interceptor-Warteschlange oder einen RTK Query baseQuery-Wrapper handelt – die Regel bleibt gleich: eine einzige Aktualisierung gleichzeitig, unabhängig davon, wie viele Anfragen warten.
9. Einstellungen, die ähnlich erscheinen, aber es nicht sind: Login Timeout und Login Action Timeout
Zwei weitere Einstellungen im Realm lassen sich leicht mit der Sitzungslaufzeit verwechseln, regeln jedoch nur den Anmeldevorgang:
- Anmeldetimer – Wie lange der gesamte Authentifizierungsablauf (Anmeldedaten, MFA, erforderliche Schritte) offen bleiben darf.
- Timer für Anmeldeaktionen – Wie lange ein einzelner Schritt (zum Beispiel ein OTP-Bildschirm) unbeantwortet bleiben darf.
Auch diese Einstellungen verlängern keine bereits authentifizierte Sitzung. Sie sind nur relevant, solange sich der Benutzer noch anmeldet.
10. Offline-Sitzungen – ein anderes Werkzeug für ein anderes Problem
Realm können außerdem über scope=offline_access Offline-Sitzungen erstellen. Diese ermöglichen neue Zugriffstoken auch nach Ablauf der normalen SSO-Sitzung – sie dienen dazu, „morgen und nächste Woche weiter eingeloggt zu bleiben“, nicht nur für einen einzelnen Arbeitstag.
Zur normalen Nutzung während der Geschäftszeiten (eine Schicht plus Puffer) ist ein Offline-Zugriff in der Regel nicht notwendig. Eine angepasste Session Max-Einstellung ist einfacher und präziser. Wählen Sie den Offline-Modus nur dann, wenn eine Langzeitspeicherung über Tage oder Wochen tatsächlich erforderlich ist.
11. Debugging-Checkliste: Wenn beim Erneuern wieder 400 angezeigt wird
Bearbeiten Sie die Schritte in dieser Reihenfolge:
- Lesen Sie den Wert von
errorsowieerror_descriptionin der Antwort. - Vergleichen Sie die Anmeldezeit mit dem Wert von
Session Maxauf Server- und Clientebene. - Vergleichen Sie die Inaktivitätsdauer mit dem Wert von
Session Idleauf Server- und Clientebene. - Prüfen Sie die vom Client vorgenommenen Anpassungen unter
Clients → <client> → Advanced. - Überprüfen Sie, ob die Rotation der Erneuerungstoken aktiviert ist.
- Überprüfen Sie, ob mehrere Anfragen gleichzeitig erneuert wurden.
- Stellen Sie sicher, dass der Client nach jeder Erneuerung das neueste Erneuerungstoken speichert, nicht nur das erste.
12. Das zu behaltende mentale Modell
Drei Ebenen, wobei die Abhängigkeiten in eine Richtung fließen:
Access token → used for normal API calls
Refresh token → used to get a new access token
Keycloak session (Idle + Max) → decides whether that refresh is allowed at all
Ein Refresh-Token ist niemals unabhängig von seiner Sitzung. Wenn die Sitzung nicht mehr vorhanden ist – zu lange inaktiv, über das maximale Alter hinaus, widerrufen oder abgemeldet – kann kein Refresh-Token sie wiederherstellen. Diese Ablehnung bedeutet, dass das System wie vorgesehen funktioniert.
Checkliste
- Betrachten Sie das Ablaufdatum des Access-Tokens getrennt vom Ende der Sitzung – versuchen Sie zunächst eine einzige Erneuerung, bevor Sie ein 401-Fehler als Abmelden interpretieren.
- Speichern Sie das erneuerte Refresh-Zertifikat bei jedem Erfolg, nicht nur den neuen Access-JWT.
- Bemerken Sie die beiden Timer: Der Aktivitäts-Timer erneuert bei Inaktivität; der vom Login gestartete Timer legt das maximale Alter fest.
- Machen Sie einen Erneuerungsfehler endgültig – eine einzige Versuch, anschließend löschen Sie die Zugangsdaten und leiten Sie den Benutzer zur Anmeldung; versuchen Sie niemals erneut in einer Schleife.