Wenn nicht authentifizierte Nutzer auf einen Link „Anmelden“ klicken, den Bezahlvorgang starten, einen Artikel zu den Favoriten hinzufügen oder eine Paywall für einen Artikel erreichen, werden sie auf eine spezielle Anmeldeseite weitergeleitet. Das führt zu einem Kontextverlust und erhöht die Abbruchrate. Der Immediate UI-Modus (uiMode: 'immediate' in navigator.credentials.get()) ermöglicht eine kontextbezogene schnelle Anmeldung, indem sofort eine einheitliche Kontoauswahl im Browser angezeigt wird, wenn auf dem Gerät lokale Passkeys (publicKey) oder gespeicherte Passwörter (password: true) vorhanden sind.
Im Gegensatz zu standardmäßigen modalen WebAuthn-Aufforderungen, bei denen immer die Browser-UI angezeigt wird und die auf geräteübergreifende QR-Codes oder externe Sicherheitsschlüssel zurückgreifen, schlägt der Immediate UI-Modus schnell mit einem NotAllowedError fehl, wenn auf dem Gerät keine geeigneten Anmeldedaten lokal verfügbar sind. So kann Ihre Anwendung wiederkehrende Nutzer mit nur einem Tippen authentifizieren und Nutzer ohne lokale Anmeldedaten nahtlos zur Standardanmeldeseite, zum Bezahlen als Gast oder zum Inline-Anmeldeformular weiterleiten.
Empfohlene UX-Muster und Antimuster
Lösen Sie den Modus „Sofortige UI“ in einem vom Nutzer initiierten Event-Handler aus (erfordert eine vorübergehende Nutzeraktivierung), wenn die Nutzerabsicht klar ist, unmittelbar bevor der Nutzer die aktuelle Seite verlässt. Wenn die sofortige Authentifizierung erfolgreich ist, führen Sie die Aktion des Nutzers sofort aus (entweder durch Aktualisieren der aktuellen Seite oder durch direktes Weiterleiten zum authentifizierten Ziel). Wenn der Vorgang mit NotAllowedError fehlschlägt, zeige einen Anmelde- oder Registrierungsablauf mit einem Pfad an, über den der Nutzer sein Vorhaben abschließen kann.
Anwendungsfälle:
- Abfangen von Klicks auf den Anmeldelink oder die Anmeldeschaltfläche im Header: Sie können einen Klick auf einen globalen Anmeldelink (
event.preventDefault()) abfangen, um wiederkehrende Nutzer sofort zu authentifizieren, oder die Navigation zur dedizierten Anmeldeseite aufNotAllowedErrorfortsetzen. - Dynamische Anmeldung vor dem Bezahlen: Authentifizieren Sie wiederkehrende Käufer mit nur einem Tippen, wenn sie auf „Zur Kasse“ klicken, damit sie direkt zum Versand- und Zahlungsschritt gelangen, oder leiten Sie sie im Fallback-Modus zur Standardanmeldung oder zum Bildschirm „Als Gast bezahlen“ weiter.
- Inline-Anmeldung für Mikroaktionen (Favorit, Like, Lesezeichen, Speichern): Authentifizieren Sie sich direkt, wenn ein nicht angemeldeter Nutzer auf eine Schaltfläche für eine Mikroaktion klickt, und schließen Sie die Aktion sofort ab (z. B. durch Umschalten von
aria-pressed="true"). Alternativ können Sie ein Fallback-Anmeldemodal oder eine Anmeldeseite mit einem Rückgabe-URL-Parameter öffnen. - Weiche Paywalls in Artikeln: Leser können sich direkt authentifizieren, wenn sie auf einem Paywall-Banner auf „Mit deinem Konto weiterlesen“ klicken. Alternativ können sie ein Inline-Anmelde- oder Registrierungsformular im Fallback-Modus maximieren.
Zu vermeidende Anti-Patterns:
- Verwenden Sie den Modus „Immediate UI“ NICHT auf einer dedizierten Anmeldeseite: Verknüpfen Sie
uiMode: 'immediate'niemals mit der primären Schaltfläche „Anmelden“ oder „Mit einem Passkey anmelden“ auf der endgültigen Anmeldeseite. Nutzer gelangen auf die spezielle Anmeldeseite, wenn sie keine lokalen Anmeldedaten auf dem aktuellen Gerät haben oder wenn sie einen geräteübergreifenden Passkey (Scannen eines QR-Codes mit einem Smartphone) oder einen externen Hardware-Sicherheitsschlüssel verwenden möchten. DauiMode: 'immediate'die geräteübergreifenden QR-Code- und Sicherheitsschlüsseloptionen unterdrückt und sofortNotAllowedErrorauslöst, wenn keine lokalen Anmeldedaten vorhanden sind, führt die Verwendung auf einer dedizierten Anmeldeseite dazu, dass der Nutzer in einer Sackgasse landet, in der das Klicken auf die Schaltfläche nichts bewirkt. Verwenden Sie auf einer dedizierten Anmeldeseite die bedingte Benutzeroberfläche (mediation: 'conditional'mitautocomplete="username webauthn") für Formulareingaben zusammen mit einer standardmäßigen modalennavigator.credentials.get({ publicKey })-Schaltfläche (siehe Passkey-Authentifizierung). - Verwenden Sie den Modus „Immediate UI“ NICHT für die erneute Authentifizierung mit erhöhten Rechten: Verwenden Sie
uiMode: 'immediate'niemals, um einen bereits angemeldeten Nutzer vor sensiblen Vorgängen (z. B. Ändern eines Passworts, Aktualisieren einer Versandadresse, Bearbeiten gespeicherter Zahlungsmethoden oder Hinzufügen eines neuen Passkeys) noch einmal zu authentifizieren. Bei der erneuten Authentifizierung muss die Aufforderung über eine nicht leereallowCredentials-Liste auf die Anmeldedaten des angemeldeten Nutzers beschränkt werden. Der Modus „Immediate UI“ lehnt dies mitNotAllowedErrorab. WennallowCredentialsweggelassen wird, kann der Nutzer versehentlich ein anderes Konto als das aktuell angemeldete auswählen. Informationen zur Step-up-Reauthentifizierung finden Sie unter Passkey-Reauthentifizierung.
Datenschutz- und Sicherheitseinschränkungen
Browser erzwingen strenge Datenschutz- und Sicherheitsregeln, wenn uiMode: 'immediate' angefordert wird:
- Vorübergehende Nutzeraktivierung erforderlich: Sie MÜSSEN
navigator.credentials.get({ uiMode: 'immediate' })innerhalb eines vom Nutzer initiierten Event-Handlers aufrufen (z. B. einesclick-Listeners für ein<button>- oder<a>-Element), um eine unbemerkte Abfrage von Anmeldedaten zu verhindern. Rufen Sie sie niemals automatisch beim Seitenaufbau auf (DOMContentLoaded). Durch den Aufruf der API wird die vorübergehende Aktivierung nicht verbraucht. - Keine nicht leere
allowCredentials-Liste übergeben: Füllen SiepublicKey.allowCredentialsNICHT aus, wennuiMode: 'immediate'angegeben ist. Browser lehnen jede Anfrage im Immediate UI-Modus mit einer nicht leerenallowCredentials-Liste sofort ab, indem sieNotAllowedErrorauslösen. So können vertrauende Seiten nicht nach bestimmten Anmeldedaten-IDs suchen, um frühere Besuche abzuleiten oder Nutzer über Sitzungen hinweg zu verfolgen. - Keine
AbortSignal(signal) übergeben: Legen Sie die Propertysignalfürnavigator.credentials.get()NICHT fest, wennuiMode: 'immediate'angegeben ist, da Browser das programmatische Schließen des Dialogfelds für die sofortige Anmeldung nicht zulassen. Wenn auf Ihrer Seite auch eine Anfrage für eine bedingte Benutzeroberfläche (mediation: 'conditional') im Hintergrund ausgeführt wird, rufen Sieabort()für diese bedingte Benutzeroberfläche (AbortController) auf, bevor Sie den Modus „Sofortige Benutzeroberfläche“ aufrufen. Lassen Siesignalin der Anfrage für die sofortige Benutzeroberfläche weg. - Inkognito- und private Browsersitzungen: Anfragen, die in Inkognito- oder privaten Browserfenstern gestellt werden, werden immer mit
NotAllowedErrorabgelehnt, sodass private Sitzungen nicht von Sitzungen ohne lokale Anmeldedaten unterschieden werden können.
Serverseitige Einrichtung
Endpunkt für die Generierung von Optionen
Stellen Sie einen Endpunkt bereit, der auffindbare WebAuthn-Anfrageoptionen für nicht authentifizierte Besucher generiert:
- Sitzungsgebundene Challenge generieren: Erstellen Sie einen kryptografisch zufälligen Challenge-Zwischenspeicher, speichern Sie ihn in der Serversitzung und codieren Sie ihn als Base64URL.
- Einstellung für die Nutzerbestätigung beibehalten: Legen Sie
userVerification: 'preferred'fest und speichern SieexpectedUserVerification: 'preferred'in der Sitzung. allowCredentialsweglassen: Fügen Sie den Optionen für den Modus „Sofortige UI“ keine nicht leereallowCredentials-Liste hinzu, da Browser nicht leere Zulassungslisten mitNotAllowedErrorablehnen.
// Server-side options generation example (/api/login/options)
router.post('/api/login/options', (req, res) => {
const options = {
challenge: serverGeneratedBase64UrlChallenge, // High-entropy random challenge stored in session
rpId: 'example.com',
userVerification: 'preferred',
};
req.session.challenge = options.challenge;
req.session.expectedUserVerification = 'preferred';
return res.json(options);
});
Endpunkte für die Bestätigung mit zwei Anmeldedaten
Da navigator.credentials.get({ password: true, publicKey, uiMode: 'immediate' }) entweder mit einem Passkey (PublicKeyCredential) oder einem gespeicherten Passwort (PasswordCredential) aufgelöst werden kann, müssen Sie Bestätigungshandler für beide Arten von Anmeldedaten bereitstellen:
- WebAuthn-Assertion-Überprüfung (
/api/login/verify): Die Assertion wird mit der Sitzungs-Challenge und dem gespeicherten öffentlichen Schlüssel abgeglichen. Wenn die Anmeldedaten-ID nicht in der Datenbank gefunden wird, wird der HTTP-Status404zurückgegeben, damit der Client den Passwortmanager überPublicKeyCredential.signalUnknownCredential()benachrichtigen kann. - Passwortbestätigung (
/api/login/password):username(cred.id) undpassword(cred.password) werden mit der Nutzerdatenbank abgeglichen und eine authentifizierte Sitzung wird eingerichtet.
Clientseitige Implementierung
Anfrage für einheitliche Anmeldedaten
- Sowohl Passkeys als auch Passwörter anfordern: Decodieren Sie die Serveroptionen mit
PublicKeyCredential.parseRequestOptionsFromJSON()und rufen Sienavigator.credentials.get()in einem vom Nutzer initiierten Klick-Handler mitpassword: true,publicKeyunduiMode: 'immediate'auf (ohnesignalzu übergeben). - Bestätigung der Filiale durch
cred.type:- Wenn
cred.type === 'public-key', serialisieren Sie die Assertion mitcred.toJSON()und senden Sie sie per POST an Ihren WebAuthn-Bestätigungsendpunkt. Wenn der Server mit HTTP404antwortet, rufen SiePublicKeyCredential.signalUnknownCredential({ rpId: publicKey.rpId, credentialId: encoded.id })auf. - Wenn
cred.type === 'password', POST{ username: cred.id, password: cred.password }an den Endpunkt für die Passwortbestätigung.
- Wenn
NotAllowedErrorverarbeiten, wenn keine lokalen Anmeldedaten vorhanden sind: Fangen SieNotAllowedErrorab (wird sofort ausgelöst, wenn keine lokalen Passkeys oder gespeicherten Passwörter auf dem Gerät vorhanden sind oder wenn der Nutzer die Auswahl schließt) und wechseln Sie zu Ihrem Standard-Anmeldeablauf (z. B. Öffnen eines Inline-Anmeldedialogs oder Weiterleitung zu/signinmit einer Rückgabe-URL).
let isAuthenticating = false;
async function authenticateWithImmediateUi() {
const optionsResponse = await fetch('/api/login/options', { method: 'POST' });
if (!optionsResponse.ok) {
throw new Error('Failed to fetch authentication options');
}
const optionsJSON = await optionsResponse.json();
const publicKey = PublicKeyCredential.parseRequestOptionsFromJSON(optionsJSON);
// MANDATORY: Request both password and publicKey with uiMode: 'immediate' and NO signal parameter
const cred = await navigator.credentials.get({
password: true,
publicKey,
uiMode: 'immediate',
});
if (!cred) {
throw new DOMException('No credential returned', 'NotAllowedError');
}
if (cred.type === 'public-key') {
const encoded = cred.toJSON();
const verifyResponse = await fetch('/api/login/verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(encoded),
});
if (verifyResponse.status === 404) {
await PublicKeyCredential.signalUnknownCredential({
rpId: publicKey.rpId || window.location.hostname,
credentialId: encoded.id, // Base64URL-encoded credential ID string
});
throw new Error('Passkey credential is no longer recognized by the server');
}
if (!verifyResponse.ok) {
throw new Error('Passkey verification failed');
}
return await verifyResponse.json();
}
if (cred.type === 'password') {
const verifyResponse = await fetch('/api/login/password', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
username: cred.id,
password: cred.password,
}),
});
if (!verifyResponse.ok) {
throw new Error('Password verification failed');
}
return await verifyResponse.json();
}
throw new Error(`Unsupported credential type: ${cred.type}`);
}
// Example: Intercepting an inline micro-action (Favorite button) or Header Sign-in click
const favoriteButton = document.getElementById('favorite-btn');
favoriteButton.addEventListener('click', async (event) => {
event.preventDefault();
if (isAuthenticating) return;
// If already signed in, toggle the micro-action state immediately
if (document.body.dataset.signedIn === 'true') {
const nextPressed = favoriteButton.getAttribute('aria-pressed') !== 'true';
favoriteButton.setAttribute('aria-pressed', String(nextPressed));
return;
}
isAuthenticating = true;
try {
const user = await authenticateWithImmediateUi();
// Complete the user's intended action immediately
document.body.dataset.signedIn = 'true';
favoriteButton.setAttribute('aria-pressed', 'true');
updateHeaderUserState(user);
} catch (err) {
if (err.name === 'NotAllowedError') {
// No local credentials available or user dismissed dialog:
// transition smoothly to fallback sign-in UI or redirect with return URL
showFallbackSignInUi({ returnAction: 'favorite' });
return;
}
console.error('Authentication error:', err);
} finally {
isAuthenticating = false;
}
});
Fallback-Strategien
Sofortiger Fallback für den UI-Modus
- Progressive Verbesserung: Behandle
uiMode: 'immediate'als progressive Verbesserung. Schließe nicht aus dem Namen oder der Version eines Browsers auf die Unterstützung. Das einzige zuverlässige Signal für die Unterstützung ist die Laufzeitprüfung voncapabilities.immediateGetüberPublicKeyCredential.getClientCapabilities(). OBLIGATORISCH: Prüfen Sie immercapabilities.immediateGet, bevor Sienavigator.credentials.get({ uiMode: 'immediate' })aufrufen. Browser, dieuiMode: 'immediate'nicht erkennen, ignorieren den unbekannten Wörterbuchschlüssel und öffnen ein blockierendes modales Dialogfeld für Passkeys und QR-Codes. Wenncapabilities.immediateGetfalsch ist odernavigator.credentials.get()mitNotAllowedErrorabgelehnt wird, greifen Sie auf die Standardanmeldung Ihrer Anwendung zurück (z. B. durch Öffnen eines Inline-Anmeldemodals oder durch Aufrufen der dedizierten Anmeldeseite mit einem Rückgabe-URL-Parameter), damit Nutzer in nicht unterstützten Browsern Ihren bestehenden Anmeldeablauf unverändert sehen:
async function isImmediateUiSupported() {
if (!window.PublicKeyCredential || !PublicKeyCredential.getClientCapabilities) {
return false;
}
try {
const capabilities = await PublicKeyCredential.getClientCapabilities();
return Boolean(capabilities?.immediateGet);
} catch {
return false;
}
}
async function authenticateWithImmediateUi() {
// MANDATORY: Check immediateGet support so unsupported browsers do not open a modal QR dialog
const supported = await isImmediateUiSupported();
if (!supported) {
// Throw NotAllowedError so the caller's single catch path treats "unsupported" like "no local credentials"
throw new DOMException('Immediate UI mode is not supported', 'NotAllowedError');
}
// Proceed with fetching options and calling navigator.credentials.get({ password: true, publicKey, uiMode: 'immediate' })
}
Fallback für die Erkennung von Clientfunktionen
Installieren Sie immer 'webauthn-polyfills' und importieren Sie es in Ihr Clientmodul, damit PublicKeyCredential.getClientCapabilities() verfügbar ist, wenn PublicKeyCredential unterstützt wird:
import 'webauthn-polyfills';
Fallback für Passwort-Anmeldedaten
Wenn Sie password: true zusammen mit publicKey in navigator.credentials.get() übergeben, können unterstützte Browser gespeicherte Passwörter aus dem Passwortmanager des Browsers in die einheitliche Kontoauswahl aufnehmen.
- Fallback-Verfahren: Browser, die
PasswordCredentialnicht unterstützen, ignorieren die Optionpassword: trueoder greifen über die Funktionsprüfungcapabilities.immediateGetauf das Standard-Anmeldeformular zurück, bei dem<input autocomplete="username webauthn">und<input type="password" autocomplete="current-password">das native automatische Ausfüllen von Passwörtern ermöglichen.
Einfacher Fallback für die JSON-Serialisierung
Installieren Sie immer 'webauthn-polyfills' und importieren Sie es in das Client-Bundle, damit PublicKeyCredential.parseRequestOptionsFromJSON() und PublicKeyCredential.prototype.toJSON() in allen Browsern, die PublicKeyCredential unterstützen, einheitlich funktionieren:
import 'webauthn-polyfills';
Fallback für die Signal API-Synchronisierung
Die WebAuthn Signal API (webauthn-signals) ist eine progressive Verbesserung, mit der veraltete Passkeys aus dem Passwortmanager des Nutzers entfernt werden, wenn der Server mit HTTP 404 antwortet.
- Fallback-Erfahrung: Aufrufe hinter
if (PublicKeyCredential.signalUnknownCredential)blockieren. Wenn die Funktion nicht unterstützt wird, überspringen Sie den Signalaufruf und zeigen Sie eine klare Meldung an, in der der Nutzer aufgefordert wird, sich mit einer alternativen Methode anzumelden.