Titelbild zum Artikel „QR-Login selbst bauen: Sitzung, Freigabe, Grenzen“
Technologie

QR-Login selbst bauen: Sitzung, Freigabe, Grenzen

6 Min. Lesezeit
QR-Code erstellen

Der Fernseher im Besprechungsraum zeigt ein Anmeldeformular und ein Buchstabenraster. Jemand steuert mit den Pfeiltasten der Fernbedienung einen Cursor, Zeichen für Zeichen, und hat gerade festgestellt, dass das Passwort einen Großbuchstaben und zwei Sonderzeichen enthält. Vier Minuten später läuft das Formular ab und leert sich selbst. Ein Anmeldecode löst dieses Problem in zwei Sekunden, und er bringt drei Pflichten mit, die vor der ersten Zeile Code feststehen sollten.

Scan-to-Login existiert wegen dieses Raums. Der Bildschirm, der nicht tippen kann, übergibt die Tipparbeit an das Gerät, das die Zugangsdaten ohnehin schon hat.

Der Bau ist nicht schwer. Das Sicherheitsmodell richtig hinzubekommen erfordert mehr Sorgfalt, als der Happy Path vermuten lässt, denn dieses Muster hat einen Fehlerfall, den keine noch so gute Kryptografie behebt.

Die Form des Handshakes

Fünf Beteiligte, der Reihe nach.

  1. Der wartende Client. Desktop-Anwendung, Smart-TV, Kiosk, alles Unauthentifizierte mit einem Bildschirm. Er bittet Ihre API, eine Login-Sitzung zu eröffnen.
  2. Ihre API. Sie legt einen Datensatz im Status "ausstehend" an und gibt zwei verschiedene Werte zurück.
  3. Der Code. Der Client stellt die öffentliche Hälfte als QR-Symbol dar. Am Muster ist nichts wirklich QR-spezifisch; das Symbol ist nur ein Transportmittel für eine kurze Kennung. Wenn Ihnen die Mechanik dieses Transports neu ist, beginnen Sie mit was ein QR-Code tatsächlich speichert.
  4. Das Telefon. Bereits angemeldet. Es scannt, liest die Kennung und fragt Ihre API, was da eigentlich freigegeben werden soll.
  5. Die Freigabe. Der Nutzer liest einen Bildschirm, der den Anfragenden benennt, tippt einmal, und der wartende Client erhält Tokens.

Zwei Werte, nicht einer

Die folgenreichste Designentscheidung steckt in Schritt 2. Wenn ein Client eine Sitzung eröffnet, geben Sie ein Paar zurück.

  • Eine Session-ID, hochentropisch und undurchsichtig, die in den QR-Code wandert.
  • Ein Client-Secret, im selben Moment erzeugt, das den wartenden Client nie verlässt und nie auf dem Bildschirm erscheint.

Jeder im Raum kann den Code an der Wand abfotografieren. Damit hat er die Session-ID. Er darf damit nicht die Tokens bekommen. Tokens werden nur an einen Aufrufer herausgegeben, der das Client-Secret vorlegt, also ist die Kennung auf dem Bildschirm ein Zeiger auf eine ausstehende Anfrage und nicht mehr.

Speichern Sie den Datensatz mit Status (pending, approved, consumed, expired, denied), created_at, expires_at, einem Hash des Client-Secrets und dem Kontext, den Sie später einem Menschen zeigen: Anwendungsname, Plattform, App-Version, grober Standort aus der Anfrage-IP und der Zeitpunkt, zu dem die Anfrage begann.

Warten, ohne zu hämmern

Der wartende Client muss nun erfahren, wann die Freigabe eintrifft. Zwei Möglichkeiten.

Polling. Alle zwei Sekunden ein GET auf den Status-Endpunkt, mit dem Client-Secret im Authorization-Header. Einfach, übersteht jeden Proxy, kostet eine Anfrage pro Client und Intervall. Begrenzen Sie die Gesamtzahl der Abfragen pro Sitzung serverseitig, statt darauf zu vertrauen, dass der Client von selbst aufhört.

Eine gehaltene Verbindung. Ein WebSocket oder ein Server-Sent-Events-Stream, geöffnet mit dem Client-Secret, der bei einem Statuswechsel eine einzige Nachricht schickt. Weniger Anfragen, mehr bewegliche Teile, und Sie wollen trotzdem einen Polling-Fallback für Netze, die lange Verbindungen abwürgen.

So oder so sollten Antworten vor der Freigabe nur einen Status und einen Countdown enthalten, sonst nichts. An eine nicht freigegebene Sitzung fließen keine Kontoinformationen.

Was das Telefon tut

Ihre mobile App scannt, extrahiert die Kennung und ruft einen Detail-Endpunkt mit dem Access-Token des angemeldeten Nutzers auf. Die Antwort ist der gespeicherte Kontext: was fragt an, von wo, seit wann. Der Scan selbst tut nichts weiter, als eine Zeichenkette zu lesen, was erwähnenswert ist, wenn Sie gewohnt sind, einen Scan als Aktion zu denken.

Dann zeigt die App einen Freigabebildschirm. Kein Toast. Kein Dialog, dessen einzige Bedienmöglichkeit eine Schaltfläche mit der Aufschrift "Weiter" ist. Ein Bildschirm, der in der Sprache des Nutzers sagt, welche Anwendung Zugriff verlangt, auf welcher Art von Gerät, von ungefähr wo und seit wann die Anfrage läuft, wobei Freigeben und Ablehnen gleich gewichtet sind.

Bei Freigabe ein POST an den Approve-Endpunkt mit der Kennung und dem Token des Nutzers. Der Server prüft vier Dinge, bevor er irgendetwas umlegt:

  • die Sitzung existiert und ist noch ausstehend
  • expires_at ist nicht überschritten
  • das freigebende Token ist gültig und gehört zu einer vollständig etablierten Sitzung, nicht zu einer, die gerade eben über denselben Ablauf entstanden ist
  • der Zustandswechsel gewinnt ein Compare-and-Set, damit nicht zwei Taps oder zwei Geräte gleichzeitig erfolgreich sein können

Dann bindet er die Freigabe an genau diesen ausstehenden Datensatz: welcher Nutzer freigegeben hat, wann, von welchem Gerät. Der nächste Statusaufruf des Inhabers des Client-Secrets liefert Tokens, und der Datensatz wechselt auf consumed. Eine Sitzung, ein Satz Tokens, kein Replay.

Ablauf ist ein Feature, keine Einstellung

Sechzig bis hundertzwanzig Sekunden. Lang genug, um das Telefon zu finden, kurz genug, dass ein Foto des Bildschirms veraltet ist, bevor es irgendwohin weitergeleitet werden kann.

Erzeugen Sie im Client neu, wenn die Zeit abläuft, und machen Sie diese Erneuerung sichtbar, damit der Nutzer weiß, dass der Code vor ihm der aktuelle ist. Abgelaufene Datensätze werden gelöscht, nicht auf ausstehend belassen. Das ist das Gegenteil eines Marketing-Codes: Ein dynamischer Code ist darauf ausgelegt, jahrelang gültig zu bleiben und umgeleitet zu werden, während ein Login-Code nach zwei Minuten tot sein und nie wiederkommen sollte.

Der Angriff, der dieses Muster definiert

Hier ist der, auf den es ankommt, und er ist kein Fehler in Ihrem Code.

Ein Angreifer eröffnet auf seinem eigenen Rechner eine Login-Sitzung. Ihr Server tut genau das Richtige und gibt eine echte Kennung zurück. Der Angreifer macht einen Screenshot des Codes und schickt ihn einem Opfer mit einer Geschichte: Bestätigen Sie Ihr Konto, bestätigen Sie die Lieferung, geben Sie die Sicherheitsprüfung der IT frei. Das Opfer scannt einen echten Code, ausgestellt von einem echten Server, und gibt ihn frei. Die Tokens werden an den wartenden Client des Angreifers geliefert, für das Konto des Opfers.

Nichts wurde gefälscht. Das Protokoll lief von Anfang bis Ende korrekt. Die einzige kompromittierte Komponente war das Verständnis des Opfers davon, wer auf der anderen Seite saß. Das gehört zur selben Familie wie Codes, die dort kleben, wo sie nicht hingehören, und es widersteht technischen Gegenmaßnahmen gerade deshalb, weil der technische Teil funktioniert hat.

Drei Designregeln wirken dagegen.

Lassen Sie eine Login-Sitzung niemals über einen Link freigeben. Wenn das Antippen einer URL in einer Nachricht Ihre Approve-Aktion erreichen kann, ist die Kamera-Zeremonie Dekoration. Beschränken Sie die Annahme der Kennung auf Ihren In-App-Scanner. Wenn eine Login-URL im Browser geöffnet wird oder als Deep Link ankommt, liefern Sie eine Erklärseite aus, auf der nirgends ein Freigabeelement steht.

Geben Sie dem Freigabebildschirm Informationsgehalt. Ein Nutzer, der "Anmeldung auf einem Smart-TV, Berlin, vor 8 Sekunden gestartet" liest, während er in einer Küche in Lissabon sitzt, hat genau die Tatsache bekommen, die er braucht. Ein Nutzer, der "Anmeldung freigeben?" liest, hat nichts bekommen.

Benachrichtigen Sie über einen zweiten Kanal. Push und E-Mail nach jeder Freigabe, mit demselben Kontext und einer Möglichkeit, die Sitzung mit einem Tippen zu beenden. Der zweite Blick fängt ab, was der erste übersehen hat, und das ist die allgemeine Lehre aus den meisten QR-Vorfällen auf Kontoebene.

Meldefrist, Protokolle und ein Weg ohne Kamera

Seit Mitte 2025 gilt in Deutschland das Barrierefreiheitsstärkungsgesetz für viele an Verbraucher gerichtete digitale Dienstleistungen. Für einen Anmeldeweg heißt das sehr konkret: Ein Ablauf, der ausschließlich mit einer Kamera funktioniert, schließt Menschen aus, und ein Ablauf, der einen Code nach dreißig Sekunden verwirft, schließt weitere aus. Bauen Sie deshalb neben dem Scan von Anfang an eine manuelle Eingabe eines kurzen Codes ein, lassen Sie die Wartezeit verlängern, statt sie hart abzuschneiden, und beschreiben Sie den Zustand des Bildschirms in Text statt nur in einer Animation. Das ist derselbe Aufwand wie ein sauberer Fehlerzustand und erspart die Nachrüstung, die sonst in einem Jahr ansteht.

Der zweite Punkt betrifft die Protokolle aus dem folgenden Abschnitt: Sie sind personenbezogene Daten. Legen Sie vor dem Start fest, welche Felder Sie wirklich brauchen, wie lange sie liegen bleiben und wer sie lesen darf, und tragen Sie das in Ihr Verzeichnis der Verarbeitungstätigkeiten ein. Die Datenschutz-Grundverordnung verlangt Maßnahmen nach dem Stand der Technik, und für einen Anmeldeweg heißt das genau die Liste, die dieser Text ohnehin empfiehlt: Bindung an das Gerät, kurze Gültigkeit, Ratenbegrenzung und ein Widerruf, der im Nachhinein greift.

Und rechnen Sie mit der Uhr, die im Schadensfall läuft. Wird Ihr Anmeldeweg für Kontoübernahmen missbraucht, ist das eine Verletzung des Schutzes personenbezogener Daten, und die ist der zuständigen Aufsichtsbehörde in der Regel binnen 72 Stunden zu melden; bei hohem Risiko für die Betroffenen sind zusätzlich diese zu informieren. Ob Sie diese Frist halten können, entscheidet sich nicht im Ernstfall, sondern beim Entwurf. Nur wenn jeder Zustandswechsel mit Zeitpunkt, Gerät und freigebender Identität protokolliert ist, können Sie in Stunden statt in Tagen sagen, welche Konten betroffen waren und welche nicht.

Rate Limits, Logs und der Rollout

Begrenzen Sie das Anlegen von Sitzungen pro IP-Adresse und pro Gerätemerkmal, denn ein offener Erzeugungs-Endpunkt ist kostenlose Aufzählung und kostenlose Last. Begrenzen Sie das Abfragen des Status pro Sitzung. Begrenzen Sie Freigaben pro Konto und Stunde. Protokollieren Sie jeden Zustandswechsel mit Anfragekontext und der Identität des Freigebenden, und legen Sie diese Protokolle dorthin, wo der Support sie lesen kann, denn das Ticket mit dem Satz, das habe ich nicht freigegeben, kommt bestimmt.

Ein Rendering-Hinweis für TV- und Kiosk-Clients: Der Code muss eine Kamera aus zwei Metern Entfernung in einem Raum mit Reflexionen überstehen. Hoher Kontrast, eine vollständige Ruhezone und eine Größe, die zur tatsächlichen Position des Sofas passt. Testen Sie gegen das Verhalten des Scanners, den Ihre Nutzer haben, nicht nur gegen Ihr eigenes Telefon.

Bringen Sie es hinter einem Flag aus, mit weiterhin verfügbarem Passwort-Login, und beobachten Sie eine Woche lang zwei Zahlen. Erzeugte Sitzungen gegenüber freigegebenen Sitzungen sagt Ihnen, ob der Code scanbar ist. Die Zeit von der Erzeugung bis zur Freigabe sagt Ihnen, ob der Freigabebildschirm Menschen verwirrt, und genau in diesem Zustand braucht ein Angreifer sie.

Diesen Artikel teilen