Passkeys und WebAuthn in Nuxt-Apps: Passwortlose Anmeldung richtig umsetzen
AI generated
{ }
Nuxt 3 · Authentifizierung
Passkeys in Nuxt: WebAuthn ohne Passwort, aber mit Fallback
Challenge-Response über Nitro-Server-Routes und wann klassische Anmeldung noch sinnvoll bleibt

Passkeys ersetzen das klassische Passwort durch ein kryptografisches Schlüsselpaar, das der Browser über die WebAuthn-API verwaltet. In diesem Artikel zeigen wir, wie eine Nuxt-Anwendung die Registrierung und Anmeldung per Passkey umsetzt, welche Rolle die Nitro-Server-Routes bei der Challenge-Generierung spielen und warum ein Fallback für ältere Browser weiterhin notwendig ist.

17 Min. Lesezeit Nuxt 3 WebAuthn

1. Was WebAuthn eigentlich ist

WebAuthn ist ein Standard des W3C, der Authentifizierung über asymmetrische Kryptografie statt über geteilte Geheimnisse wie Passwörter ermöglicht. Beim Registrieren erzeugt ein Authenticator, etwa der Fingerabdrucksensor eines Laptops, Windows Hello oder ein separater Sicherheitsschlüssel, ein Schlüsselpaar, dessen privater Teil den Authenticator niemals verlässt. Der Server speichert ausschließlich den öffentlichen Schlüssel und kann damit später jede Signatur prüfen, ohne selbst ein Geheimnis vorhalten zu müssen, das gestohlen werden könnte.

Der Begriff Passkey bezeichnet dabei eine bestimmte, nutzerfreundliche Ausprägung einer WebAuthn-Credential: eine sogenannte discoverable Credential, die sich der Browser merkt und die über ein Plattformkonto wie die iCloud-Schlüsselbund-Funktion oder den Google-Passwortmanager automatisch zwischen den Geräten eines Nutzers synchronisiert. Ältere, nicht-discoverable WebAuthn-Credentials blieben dagegen an ein einzelnes Gerät gebunden und mussten pro Gerät einzeln registriert werden, was für die breite Nutzung deutlich unpraktischer war.

2. Vergleich zu klassischer Passwort-plus-2FA-Authentifizierung

Klassische Anmeldung mit Passwort und zweitem Faktor bleibt trotz zusätzlichem Schutz anfällig für Phishing, denn ein Passwort lässt sich auf einer gefälschten Seite genauso eintippen wie auf der echten, und auch ein per SMS oder App generierter Einmalcode lässt sich in Echtzeit an einen Angreifer weiterleiten. WebAuthn-Signaturen sind dagegen fest an die Origin gebunden, unter der sie erzeugt wurden, sodass eine Signatur, die für mironsoft.de erstellt wurde, auf keiner anderen Domain jemals gültig ist, selbst wenn die gefälschte Seite optisch identisch aussieht.

Auch beim Bedienkomfort liegt der Vorteil meist bei Passkeys: ein einziger biometrischer Prompt oder eine kurze PIN-Eingabe ersetzt das Eintippen eines Passworts und das anschließende Abrufen eines Codes aus einer separaten App oder SMS. Der Umstieg bringt allerdings eigene UX-Herausforderungen mit sich, etwa eine klare Führung beim erstmaligen Einrichten auf einem neuen Gerät sowie einen durchdachten Wiederherstellungsweg für den Fall, dass alle registrierten Geräte gleichzeitig verloren gehen.

3. Serverseitige Challenge-Generierung über Nitro

Der Registrierungsablauf beginnt serverseitig: der Server erzeugt eine zufällige, nur einmal gültige Challenge und baut daraus zusammen mit Angaben zu Nutzer und Relying Party die publicKeyCredentialCreationOptions, die an den Client geschickt werden. Der Browser ruft damit navigator.credentials.create() auf, lässt den Nutzer den Authenticator bestätigen und schickt die entstandene Attestation-Response zurück an den Server, der sie verifiziert und bei Erfolg den öffentlichen Schlüssel zusammen mit der Credential-ID dauerhaft speichert.

In der Praxis übernimmt eine Bibliothek wie simplewebauthn diese kryptografische Detailarbeit sowohl beim Erzeugen der Optionen als auch bei der späteren Verifikation, sodass eigener Code sich vor allem um Speicherung und Anbindung an das Nutzerkonto kümmert. Der folgende Ausschnitt zeigt eine Nitro-Serverroute, die die Registrierungsoptionen erzeugt und die zugehörige Challenge kurzlebig im Nitro-Storage ablegt, damit sie beim nächsten Schritt gegengeprüft werden kann.


// server/api/webauthn/register-options.post.ts
import { generateRegistrationOptions } from '@simplewebauthn/server'
import { defineEventHandler, readBody } from 'h3'

export default defineEventHandler(async (event) => {
  const { userId, username } = await readBody(event)

  const options = await generateRegistrationOptions({
    rpName: 'Mironsoft Shop',
    rpID: 'mironsoft.de',
    userID: Buffer.from(userId),
    userName: username,
    attestationType: 'none',
    authenticatorSelection: {
      residentKey: 'required',
      userVerification: 'preferred'
    }
  })

  await useStorage('webauthn-challenges').setItem(userId, options.challenge)

  return options
})

4. Registrierung im Client mit navigator.credentials

Auf der Client-Seite ruft die Funktion startRegistration aus dem Paket @simplewebauthn/browser die zuvor vom Server gelieferten Optionen entgegen und kümmert sich um den korrekten Aufruf von navigator.credentials.create(), inklusive der nötigen Base64URL-Kodierung der binären Felder. Der Browser zeigt daraufhin den plattformeigenen Prompt an, etwa den Fingerabdrucksensor oder Windows Hello, und liefert bei Erfolg eine Attestation-Response zurück, die anschließend unverändert an eine zweite Serverroute geschickt wird.

Diese zweite Route ruft verifyRegistrationResponse auf, vergleicht die im Response enthaltene Challenge mit dem zuvor gespeicherten Wert, prüft Origin und rpID auf Übereinstimmung und extrahiert im Erfolgsfall den öffentlichen Schlüssel. Erst danach wird die neue Credential, bestehend aus Credential-ID, öffentlichem Schlüssel und einem Signatur-Zähler, dauerhaft in der Datenbank mit dem betreffenden Nutzerkonto verknüpft.

5. Anmeldung und Verifikation der Assertion

Beim Anmelden erzeugt der Server erneut eine frische Challenge, diesmal zusammen mit einer allowCredentials-Liste, die auf die für den Nutzer bekannten Credential-IDs verweist, oder, im usernamelosen Fall, ganz ohne diese Liste. Der Browser ruft navigator.credentials.get() auf, der Nutzer wählt seinen Passkey aus und der Authenticator signiert die Challenge mit dem zuvor niemals übertragenen privaten Schlüssel, ohne dass dieser den Authenticator jemals verlassen muss.

Serverseitig prüft verifyAuthenticationResponse die Signatur gegen den gespeicherten öffentlichen Schlüssel und vergleicht außerdem den mitgelieferten Signatur-Zähler mit dem zuletzt gespeicherten Wert, um geklonte Authenticatoren zu erkennen. Fällt die Prüfung positiv aus, stellt die Anwendung wie gewohnt eine Session aus, etwa über ein httpOnly-Cookie, und der Passkey-Login ist damit vollständig abgeschlossen.

6. Fallback-Strategie für Browser ohne WebAuthn-Support

Ob ein Browser WebAuthn überhaupt unterstützt, lässt sich einfach per Feature-Detection anhand von window.PublicKeyCredential prüfen, zusätzlich liefert PublicKeyCredential.isConditionalMediationAvailable() Auskunft darüber, ob auch die Autofill-Variante zur Verfügung steht. Fehlt eine der beiden Fähigkeiten, sollte die Anwendung ohne Umschweife auf ein klassisches Anmeldeformular zurückfallen, statt den Nutzer mit einer fehlschlagenden Passkey-Anfrage zu konfrontieren.

In der Praxis empfiehlt es sich, Passwort- oder Magic-Link-Anmeldung dauerhaft als parallele Methode anzubieten statt Passkeys zu erzwingen, denn ältere Browser, manche eingebetteten In-App-Webviews sowie stark abgesicherte Unternehmensumgebungen unterstützen WebAuthn teilweise nach wie vor nicht vollständig. Ein erzwungener Passkey-Only-Ansatz würde diese Nutzergruppe schlicht von der Anwendung aussperren.

7. Usernameless Login und Conditional UI

Weil discoverable Credentials direkt im Browser oder in der Systemschlüsselverwaltung hinterlegt sind, erlaubt WebAuthn eine Anmeldung, ohne vorher einen Nutzernamen einzutippen: der Browser zeigt stattdessen eine Auswahl der für die aktuelle Relying Party gespeicherten Passkeys an, aus der der Nutzer den passenden auswählt. Diese sogenannte Usernameless- oder Discoverable-Login-Variante reduziert die Anzahl der nötigen Interaktionsschritte weiter.

Die Conditional-UI-Erweiterung geht noch einen Schritt weiter und blendet passende Passkeys bereits als Autofill-Vorschlag direkt im gewöhnlichen Username-Eingabefeld ein. Technisch wird dazu navigator.credentials.get() bereits beim Laden der Seite mit der Option mediation: 'conditional' aufgerufen, ohne dass der Nutzer vorher eine Aktion auslösen muss, der Browser wartet dann einfach im Hintergrund auf eine Auswahl.

8. Account-Wiederherstellung und Multi-Device-Handling

Jede Anwendung mit Passkey-Login braucht einen Weg für den Fall, dass ein Nutzer sämtliche registrierten Geräte gleichzeitig verliert, etwa eine klassische E-Mail-Verifikation, Backup-Codes oder einen manuellen Support-Prozess. Ebenso wichtig ist eine Verwaltungsoberfläche, über die Nutzer mehrere Passkeys für unterschiedliche Geräte registrieren und einzelne davon bei Bedarf wieder entfernen können.

Plattform-Passkeys synchronisieren innerhalb eines Ökosystems meist automatisch, etwa über die iCloud-Schlüsselbund-Funktion zwischen allen Apple-Geräten eines Kontos oder über den Google-Passwortmanager zwischen Android-Geräten. Der Wechsel zwischen unterschiedlichen Ökosystemen, etwa von einem Android-Handy zu einem Windows-Rechner, läuft dagegen über den sogenannten Hybrid-Transport per QR-Code. Für jede registrierte Credential sollte die Datenbank zusätzlich Metadaten wie Gerätename und Erstellungsdatum speichern, damit die Verwaltungsoberfläche verständlich bleibt.

9. Sicherheitsaspekte und Betriebs-Checkliste

Die rpID muss exakt der Domain entsprechen, unter der die Anwendung ausgeliefert wird, andernfalls schlägt bereits die Prüfung im Browser fehl, bevor überhaupt eine Anfrage den Server erreicht. Ebenso zwingend ist die serverseitige Prüfung der Origin bei jeder Verifikation, außerdem sollten gespeicherte Challenges nur wenige Minuten gültig und nach der Nutzung sofort wieder gelöscht werden, um Replay-Angriffe auszuschließen.

Zusätzlich empfiehlt sich Rate-Limiting für Registrierungs- und Anmelde-Endpunkte, ein waches Auge auf Anomalien beim Signatur-Zähler sowie regelmäßige Updates der eingesetzten WebAuthn-Bibliothek, da sich Details der Spezifikation und der Browser-Implementierungen weiterentwickeln. Vor jedem Rollout lohnt sich außerdem ein Test auf den wichtigsten Plattformen, also Chrome auf Android, Safari auf iOS sowie Windows Hello, da sich kleinere Verhaltensunterschiede zwischen den Implementierungen durchaus zeigen können.

Aspekt Passwort + 2FA Passkey (WebAuthn)
Phishing-Resistenz Passwort abtippbar, OTP abfangbar An Origin gebunden, praktisch phishing-resistent
Nutzer-Aufwand Passwort merken plus Code abrufen Ein Biometrie-Prompt oder eine PIN
Serverseitige Speicherung Passwort-Hash plus Secret für 2FA Nur öffentlicher Schlüssel, kein Geheimnis
Geräteverlust Reset per E-Mail meist ausreichend Weiteres registriertes Gerät oder Recovery-Weg nötig
Browser-Unterstützung Universell Moderne Browser, Fallback weiterhin nötig

Mironsoft

Vue-Architektur, Composition API und Nuxt-Performance

Vue-Anwendungen, die mit jedem Feature nicht komplizierter werden?

Wir prüfen bestehende Vue- und Nuxt-Projekte auf unstrukturierte Composables, ungenutzte Reaktivität und aufgeblähte Bundles und bauen daraus eine Architektur, die neue Features aufnimmt, ohne die Codebasis unübersichtlicher zu machen.

Architektur-Review

Composables, State-Management und Komponentenstruktur auf Wartbarkeit prüfen.

Performance-Audit

Reaktivitäts-Overhead, Bundle-Größe und Nuxt-Rendering-Strategie systematisch optimieren.

Nuxt-Integration

SSR/SSG-Setup und API-Anbindung robust und typsicher aufbauen.

10. Zusammenfassung

Passkeys und WebAuthn in Nuxt: Das Wichtigste auf einen Blick

Standard

WebAuthn (W3C), Passkey als nutzerfreundliches Branding einer discoverable Credential.

Serverseitig

Challenge-Generierung und Verifikation über Nitro-Server-Routes.

Sicherheit

An die Origin gebunden, dadurch praktisch phishing-resistent.

Fallback

Passwort- oder Magic-Link-Anmeldung für nicht unterstützte Umgebungen.

11. FAQ: Passkeys und WebAuthn in Nuxt: Das Wichtigste auf einen Blick

1Was ist der Unterschied zwischen einem Passkey und einer klassischen WebAuthn-Credential?
Ein Passkey ist eine WebAuthn-Credential, die als discoverable Credential angelegt ist und über ein Plattformkonto wie iCloud-Schlüsselbund oder den Google-Passwortmanager zwischen Geräten synchronisiert wird, während ältere WebAuthn-Credentials oft an ein einzelnes Gerät gebunden blieben.
2Muss ich WebAuthn selbst von Grund auf implementieren?
Nein, Bibliotheken wie simplewebauthn kapseln die kryptografische Verifikation von Attestation und Assertion serverseitig sowie die Aufrufe der Browser-API clientseitig, sodass eigener Code sich vor allem um Optionen, Speicherung und Session-Verwaltung kümmert.
3Warum braucht WebAuthn eine serverseitig generierte Challenge?
Die Challenge verhindert Replay-Angriffe, da jede Registrierung und jede Anmeldung eine neue, zufällige und nur einmal gültige Zeichenfolge signiert, die der Server anschließend gegen den gespeicherten Wert prüft.
4Was passiert, wenn ein Nutzer alle seine Geräte verliert?
Ohne registriertes Backup-Gerät braucht es einen alternativen Wiederherstellungsweg wie E-Mail-Verifikation oder einen Support-Prozess, deshalb sollte eine Anwendung immer mindestens eine Recovery-Option parallel zum Passkey anbieten.
5Kann ich Passwort-Login komplett abschaffen?
Technisch möglich, aber wegen unterschiedlicher Geräte- und Browser-Landschaften sowie Unternehmensumgebungen empfiehlt sich meist eine Übergangsphase, in der Passkey als bevorzugte und Passwort als Fallback-Methode parallel existieren.
6Was ist Conditional UI bei WebAuthn?
Conditional UI erlaubt es dem Browser, passende Passkeys direkt als Autofill-Vorschlag im Username-Feld anzuzeigen, ohne dass der Nutzer vorher explizit einen Login-Button betätigen muss.
7Wie erkenne ich geklonte Authenticatoren?
Jede Assertion enthält einen Signatur-Zähler, steigt dieser nicht monoton gegenüber dem zuletzt gespeicherten Wert, deutet das auf eine geklonte oder manipulierte Credential hin und die Anmeldung sollte abgelehnt werden.
8Muss die rpID exakt der Domain entsprechen?
Ja, die rpID muss der Domain oder einer übergeordneten registrierbaren Domain entsprechen, unter der die Anwendung ausgeliefert wird, sonst schlägt die Verifikation bereits im Browser fehl.
9Funktionieren Passkeys auch in nativen Apps oder Webviews?
Das hängt vom jeweiligen Webview und Betriebssystem ab, manche eingebetteten Webviews unterstützen WebAuthn nicht vollständig, weshalb dort ein Fallback auf Passwort oder ein Öffnen im Systembrowser sinnvoll ist.
10Wo werden die Nutzerdaten bei einem Passkey gespeichert?
Der private Schlüssel verlässt nie das Gerät beziehungsweise den sicheren Speicher des Authenticators, der Server speichert ausschließlich den öffentlichen Schlüssel sowie eine Credential-ID pro registriertem Gerät.