Ponowne uwierzytelnianie za pomocą klucza dostępu

Funkcje internetowe: Uwierzytelnianie internetowe

Ten przewodnik skupiający się na różnicach szczegółowo opisuje, jak wdrożyć uwierzytelnianie wielopoziomowe lub ponowną weryfikację zalogowanego użytkownika, zanim wprowadzi on poufne zmiany na koncie (np. aktualizacje haseł, przelewy finansowe).

Architektura Delta Flow

W odróżnieniu od zwykłego uwierzytelniania ponowne uwierzytelnianie za pomocą klucza dostępu ogranicza wyświetlanie okien dialogowych klucza dostępu wyłącznie do wstępnie zarejestrowanych danych logowania zalogowanego użytkownika, aby zapobiec mieszaniu kont lub fałszowaniu kluczy dostępu podczas aktywnych sesji.

Po stronie serwera

Delta generowania opcji

Utwórz punkt końcowy, który wypełnia parametry dozwolonych danych logowania, zwłaszcza w przypadku aktywnego, znanego użytkownika:

Ograniczanie danych logowania: wypełnij tablicę opcji allowCredentials określonymi rekordami PublicKeyCredentialDescriptor, które mapują wszystkie zarejestrowane identyfikatory danych logowania zalogowanego użytkownika. Pozostawienie tego pola pustego lub pominięcie go powoduje powrót do danych logowania możliwych do wykrycia, co narusza bezpieczeństwo sesji.

// Node.js step-up options generation example
router.post("/api/reauth/options", enforceActiveSession, async (req, res) => {
  const userPasskeys = await db.findCredentialsByUserId(req.user.id);

  const options = {
    challenge: serverGeneratedBase64UrlChallenge, // Random challenge stored in user session
    rpId: "example.com",
    // Enforce allowance strictly limited to the user's credentials list
    allowCredentials: userPasskeys.map((cred) => ({
      type: "public-key",
      id: cred.id,
      transports: cred.transports, // Speeds up resolution by indicating platform transports
    })),
  };
  return res.json(options);
});

Różnica w punktach końcowych weryfikacji

Zweryfikuj asercję zwróconą przez klienta:

Weryfikacja własności konta: punkt końcowy weryfikacji MUSI wyraźnie potwierdzić, że wynikowy uwierzytelniony identyfikator danych logowania zwrócony przez klienta odpowiada przechowywanemu rekordowi danych logowania, którego powiązany identyfikator użytkownika ściśle pasuje do aktywnego zalogowanego użytkownika (storedCredential.passkeyUserId === req.user.id). Jeśli zostanie zwrócony prawidłowy klucz dostępu innego użytkownika, uwierzytelnianie MUSI zostać natychmiast odrzucone.

Różnice w procesie po stronie klienta

Aplikacje wybierają jeden z 2 interfejsów ponownego uwierzytelniania w zależności od interfejsu transakcji:

A. Proces z przyciskiem (bez pól do wprowadzania danych)

Uruchom ponowne uwierzytelnianie, gdy użytkownik naciśnie przycisk „Potwierdź tożsamość” lub „Kontynuuj transakcję”.

<button id="reauth-btn" data-testid="reauth-button">Confirm Transaction</button>
let reauthAbortController = new AbortController();

async function triggerButtonReauth() {
  // Abort any background suggestion flows to avoid passkey prompt collisions
  reauthAbortController.abort();
  reauthAbortController = new AbortController();

  const optionsResponse = await fetch("/api/reauth/options", {
    method: "POST",
  });
  const optionsJSON = await optionsResponse.json();
  const publicKey =
    PublicKeyCredential.parseRequestOptionsFromJSON(optionsJSON);

  try {
    const credential = await navigator.credentials.get({
      publicKey,
      signal: reauthAbortController.signal,
    });

    if (credential) {
      const encodedCredential = credential.toJSON();
      const verifyResponse = await fetch("/api/reauth/verify", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(encodedCredential),
      });

      if (verifyResponse.ok) {
        showTransactionSuccessUI();
      } else if (verifyResponse.status === 404 && PublicKeyCredential.signalUnknownCredential) {
        await PublicKeyCredential.signalUnknownCredential({
          rpId, // RP ID must match the one defined on the server
          credentialId: encodedCredential.id
        });
      }
    }
  } catch (err) {
    if (err.name === "NotAllowedError") {
      console.log("User cancelled reauthentication.");
    }
  }
}

document
  .getElementById("reauth-btn")
  .addEventListener("click", triggerButtonReauth);

Strategie zastępcze

Zastępcze wykrywanie funkcji klucza dostępu

Zawsze instaluj „webauthn-polyfills” i importuj je w kontekście. Pamiętaj, że jeśli obsługiwany jest format PublicKeyCredential, obsługiwany jest też format PublicKeyCredential.getClientCapabilities.

import 'webauthn-polyfills';

Łatwa serializacja JSON – kreacja zastępcza

Zawsze instaluj „webauthn-polyfills” i importuj je w kontekście. Jeśli obsługiwany jest format PublicKeyCredential, obsługiwane są też formaty PublicKeyCredential.parseRequestOptionsFromJSON i PublicKeyCredential.prototype.toJSON.

import 'webauthn-polyfills';