TOTP-Hardware-Token in Keycloak vorprovisionieren – so geht’s trotzdem!

Inhaltsverzeichnis

Einleitung

Viele Unternehmen setzen auf Hardware-Tokens für die Zwei-Faktor-Authentifizierung (2FA). In der Praxis taucht dabei schnell eine Hürde auf: Wie kann ein TOTP-Token zentral provisioniert werden, ohne dass der Benutzer selbst den Seed manuell registrieren muss?

Problem: Keycloak erlaubt keine zentrale TOTP-Registrierung

Standardmäßig ist Keycloak darauf ausgelegt, dass Benutzer ihren TOTP-Token selbst registrieren, z. B. durch das Scannen eines QR-Codes beim ersten Login. Das ist zwar sicher, aber in manchen Organisationen nicht praktikabel:

  • Die Registrierung ist zu kompliziert – insbesondere für wenig technikaffine Anwender oder externe Mitarbeitende.
  • Hardware-Token sollen vor der Übergabe konfiguriert werden, damit der Benutzer direkt arbeitsfähig ist.
  • Eine zentrale Registration durch IT oder eine Registrierungsstelle ist gewünscht, etwa im Rahmen einer ITIL-konformen Prozesseinführung.

Das Problem: Weder per Admin-Konsole noch über die REST-API lässt sich der TOTP-Seed für einen Benutzer setzen.

Warum das wichtig ist

Eine zentrale Vorkonfiguration bietet klare Vorteile:

  • Kein manuelles Scannen von QR-Codes
  • Ideal für die Erstverteilung von Hardware-Tokens
  • Sicher, nachvollziehbar und in bestehende IT-Prozesse integrierbar

In diesem Blogpost zeige ich dir, wie man Keycloak um genau diese Funktionalität erweitert – auch wenn sie im Standard nicht vorgesehen ist.

Lösung: Custom API + Impersonation + Token Exchange

Mit einer kleinen Erweiterung und Konfigurationen in Keycloak lässt sich dieses Problem dennoch lösen. Ein vertraulicher Client (confidential client) ruft im Namen eines Benutzers (Impersonation) einen Custom-Endpoint auf, um dessen TOTP-Seed zu setzen – ohne Passwort oder Benutzerinteraktion.

Beispiel-Call:

POST /realms/<realm>/totp-apis/createTotp
Authorization: Bearer <User-Access-Token>
Content-Type: application/json

{"secret": "<BASE32-TOTP-SECRET>"}

Schritt-für-Schritt-Anleitung

Alle Schritte wurden unter Keycloak 26.3.2 nachvollzogen. Abweichungen in älteren oder neueren Versionen sind nicht ausgeschlossen.

1. Extension installieren

Diese kleine, aber wirkungsvolle Keycloak-Extension erweitert die REST-API um einen neuen Endpoint, über den TOTP-Secrets direkt für Benutzer gesetzt werden können – etwas, das Keycloak standardmäßig nicht erlaubt. Sie wird als Provider eingebunden.

https://github.com/shogunassar/keycloak-totp-custom-api/
  • Repository auschecken und jar-File bauen (mvn clean install)
  • JAR in ${KEYCLOAK_HOME}/providers/ ablegen (Docker: /opt/keycloak/providers)
  • Danach: bin/kc.sh build + Keycloak neustarten
  • Prüfung: In der Admin-Konsole unter „Server Info → Provider Info → realm-resource“ muss totp-apis erscheinen:

Alternativ kann der neue Endpunkt auch z.B per cURL getestet werden:

curl -i https://<your-keycloak>/realms/<realm>/totp-apis/createTotp
# Erwartet wird: HTTP 401 (nicht 404!)

Ein HTTP 401 Unauthorized zeigt an, dass der Endpoint korrekt registriert wurde, aber ein gültiger Token fehlt. Erhalten wir stattdessen ein HTTP 404 Not Found, ist die Extension vermutlich nicht geladen – bitte die Installationsschritte erneut prüfen.

2. Features aktivieren

Der Client muss den Benutzer „impersonieren“, d. h. das TOTP-Secret im Kontext des Benutzers setzen. Dafür benötigen wir zwei zentrale Features in Keycloak:

  • Token Exchange – um ein Benutzer-Token zu erhalten, obwohl kein Login stattfindet
  • Fine-Grained Admin Permissions (FGAP) – um präzise zu steuern, welcher Client welchen Benutzer impersonieren darf

Diese Features müssen explizit aktiviert werden – z. B. per Startparameter:

--features="token-exchange,admin-fine-grained-authz:v1"

Danach:

bin/kc.sh build

Wer ist „der Client“?

Der „Client“ in diesem Kontext ist ein technischer Service-Client, der den REST_Aufruf zum Setzen des TOTPs durchführt. Im weiteren Verlauf zeigen wir ein Beispiel in Form eines kleinen Python-Skripts, das genau diesen Ablauf übernimmt. Das Skript:

  • holt sich zunächst ein Access Token über den Client-Credentials-Flow,
  • verwendet Token Exchange, um ein Benutzer-Token zu erhalten (requested_subject)
  • und ruft anschließend den Custom-Endpoint zum Setzen des TOTP-Secrets auf – im Kontext des impersonierten Benutzers.

Warum brauchen wir „Token Exchange“?

Das Feature Token Exchange ermöglicht es einem technischen Client (z. B. Service-Account), im Rahmen eines „Impersonation“-Flows ein Benutzer-Access-Token zu erhalten – ohne Passwort und ohne Benutzerinteraktion.

In unserem Szenario ist das entscheidend, weil:

  • Nur ein Benutzer-Access-Token berechtigt den Aufruf des TOTP-Endpoints.
  • Unser System-Client muss im Namen eines bestimmten Benutzers handeln.
  • Über Token Exchange können wir ein Client-Credentials-Token gegen ein Benutzer-Token tauschen (requested_subject) obwohl der Benutzer gar nicht eingeloggt ist.

Token Exchange ist also die technische Grundlage dafür, dass der Service-Client zentral TOTP-Secrets setzen kann.

3. Confidential Client einrichten

Als nächtes richten wir ein Client in Keyclaok ein. Wichtig sind dabei folgende Parameter:

  • Client Authentication: Aktiviert
  • Service Accounts Roles: Aktiviert
  • Standard Token Exchange: Deaktiviert
Warum muss „Standard Token Exchange“ deaktiviert sein?

Wenn „Standard Token Exchange“ aktiviert ist, verwendet Keycloak intern den v2-Token-Exchange-Mechanismus, der den Parameter requested_subject nicht unterstützt. Unser Szenario basiert aber auf dem sogenannten Legacy Token Exchange (v1), bei dem genau dieser Parameter benötigt wird, um ein Token für einen bestimmten Benutzer zu erhalten. Wird requested_subject bei aktiviertem v2-Exchange verwendet, schlägt der Austausch fehl – meist mit einer Fehlermeldung wie Invalid parameter oder not supported. Deshalb: Unbedingt „Standard Token Exchange“ deaktivieren, damit unser Client über das Legacy-Verfahren arbeiten kann.

Als nächstes geben wir dem Client noch die Rolle Impersonation:

Wer nun denkt, dass die Rolle Impersonation dem Client bereits die vollständige Berechtigung zum Impersonieren verleiht, liegt leider falsch. Diese Rolle erlaubt lediglich den technischen Zugriff auf die Impersonation-API.
Ob der Client tatsächlich bestimmte Benutzer impersonieren darf, wird jedoch separat über FGAP-Policies gesteuert. Schauen wir uns das im nächsten Schritt genauer an.

4. Impersonation freischalten

Zunächste müssen wir die Fine-Grained-Access-Permissions (FGAP) für Nutzer aktivieren:

  • Users → Permissions → Enable
  • in der darauf erscheinenden Liste klicken wir auf impersonate
  • hier konfigurieren wir folgende Permission:
  • Die referenzierte Policy policy-allow-totp-client muss zunächst erstellt werden. Sie definiert einfach, welcher Client zur Impersonation berechtigt ist – in unserem Fall der Client, der das TOTP-Secret setzen soll.

5. Tokens austauschen & TOTP setzen (Python-Skript)

Um den gesamten Ablauf zu testen und zu demonstrieren, verwenden wir ein kleines Python-Skript. Dieses steht stellvertretend für jede beliebige Anwendung, die später automatisiert TOTP-Secrets setzen soll – z. B. eine Registrierungsanwendung, ein Onboarding-Tool oder ein Self-Service-Portal im Unternehmen.

Das Skript übernimmt folgende Schritte:

  • Holt ein Access Token über den Client-Credentials-Flow für unseren technischen Service-Client.
  • Tauscht dieses Token gegen ein Benutzer-Access-Token mithilfe von Legacy Token Exchange (requested_subject).
  • Ruft den Custom-Endpoint /createTotp auf, um das TOTP-Secret im Kontext des Zielbenutzers zu setzen.
#!/usr/bin/env python3
import json
import requests

BASE = "<your-keycloak-hostname>"
REALM = "<your-keycloak-realm>"

CLIENT_ID = "<client-id>"
CLIENT_SECRET = "<client-secret>" 

TARGET_USER = "<Username in Keycloak for which the TOTP secret should be set>"  # für requested_subject (Legacy Impersonation)
TOTP_SECRET_BASE32 = "<the TOTP secret>" # e.g. "JBSWY32PEHPK3PXP" 

def client_token():
    url = f"{BASE}/realms/{REALM}/protocol/openid-connect/token"
    r = requests.post(url, data={
        "grant_type": "client_credentials",
        "client_id": CLIENT_ID,
        "client_secret": CLIENT_SECRET
    }, timeout=30)
    r.raise_for_status()
    return r.json()["access_token"]

def exchange_to_user(requested_subject):
    # Legacy Token Exchange (Impersonation), Standard-V2 muss am Client AUS sein
    url = f"{BASE}/realms/{REALM}/protocol/openid-connect/token"
    r = requests.post(url, data={
        "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange",
        "client_id": CLIENT_ID,
        "client_secret": CLIENT_SECRET,
        "requested_subject": requested_subject
    }, timeout=30)
    r.raise_for_status()
    return r.json()["access_token"]

def create_totp(user_access_token, secret_base32):
    url = f"{BASE}/realms/{REALM}/totp-apis/createTotp"  # <== wichtiger Pfad
    r = requests.post(
        url,
        headers={
            "Authorization": f"Bearer {user_access_token}",
            "Content-Type": "application/json",
        },
        json={"secret": secret_base32},  # <== flacher Body
        timeout=30
    )
    return r

if __name__ == "__main__":
    ct = client_token()
    ut = exchange_to_user(TARGET_USER)
    resp = create_totp(ut, TOTP_SECRET_BASE32)
    print("createTotp ->", resp.status_code)
    try:
        print(resp.json())
    except Exception:
        print(resp.text)

Probieren wir es aus. Wir legen einen Benutzer testuser01 an – dieser hat zunächst kein TOTP eingerichtet:

Anschließend führen wir das Skript aus und setzen testuser01 als TARGET_USER.

python3 create_totp.py
createTotp -> 200

Ein Blick in die Keycloak-Benutzerverwaltung bestätigt:

Ausblick: Integration in ein Registrierungs-Tool

Die hier gezeigte Lösung lässt sich sehr einfach in ein eigenes Tool integrieren – z. B. für den Einsatz im Rahmen einer Benutzerregistrierung oder IT-Onboarding-Prozesse. Ein solches Tool könnte folgende Schritte automatisieren:

  • TOTP-Secret generieren – z. B. in Base32 (kompatibel mit OATH-Tokens)
  • TOTP-Secret per API in Keycloak hinterlegen – wie oben beschrieben, über den /createTotp-Endpoint
  • TOTP-Secret auf ein Hardware-Token schreiben – viele Hersteller von OATH-Tokens (z. B. Token2) bieten hierfür kleine Kommandozeilentools an, mit denen sich Seeds auf ein Token übertragen lassen

Damit entsteht ein vollständig automatisierter Ablauf:

  • Ein IT-Mitarbeiter oder ein Self-Service-Portal startet das Tool
  • Das Tool generiert und setzt das Secret
  • Das Secret wird direkt auf den Hardware-Token geschrieben
  • Der Token kann anschließend dem Benutzer übergeben werden – einsatzbereit und vorkonfiguriert

Vorteile

  • Kein manuelles Scannen von QR-Codes mehr
  • Ideal für die Initialverteilung von TOTP-Token
  • Sicher und nachvollziehbar – lässt sich in bestehende Prozesse integrieren (z. B. ITIL, IAM, Identity-Provisioning)

Nach oben scrollen