Riautenticazione con passkey

Funzionalità web: Autenticazione web

Questa guida incentrata sulle differenze spiega in dettaglio come implementare l'autenticazione step-up o la nuova verifica per un utente che ha eseguito l'accesso prima che apporti modifiche sensibili all'account (ad es. aggiornamenti delle password, trasferimenti finanziari).

Architettura del flusso Delta

A differenza dell'autenticazione standard, la riautenticazione con passkey limita le richieste della finestra di dialogo della passkey esclusivamente alle credenziali preregistrate dell'utente che ha eseguito l'accesso, per evitare la combinazione di account o lo spoofing della passkey durante le sessioni attive.

Lato server

Delta di generazione delle opzioni

Crea un endpoint che compili i parametri delle credenziali consentite specificamente per l'utente attivo e noto:

Vincola le credenziali: compila l'array di opzioni allowCredentials con record PublicKeyCredentialDescriptor specifici che mappano tutti gli ID delle credenziali registrate per l'utente che ha eseguito l'accesso. Se questo campo viene lasciato vuoto o omesso, si torna alle credenziali rilevabili, violando la sicurezza della sessione.

// 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);
});

Delta endpoint di verifica

Verifica l'asserzione restituita dal client:

Verifica della proprietà dell'account: l'endpoint di verifica DEVE verificare esplicitamente che l'ID della credenziale autenticata restituito dal client corrisponda a un record della credenziale memorizzato il cui ID utente associato corrisponda esattamente all'utente che ha eseguito l'accesso (storedCredential.passkeyUserId === req.user.id). Se viene restituita una passkey valida di un utente diverso, l'autenticazione DEVE essere rifiutata immediatamente.

Delta del flusso lato client

Le applicazioni scelgono tra due interfacce di riautenticazione a seconda della UI della transazione:

A. Flusso di pulsanti (nessun campo di immissione)

Attiva la riautenticazione quando un utente preme un pulsante "Verifica identità" o "Procedi con la transazione".

<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 di fallback

Fallback del rilevamento delle funzionalità delle passkey

Installa sempre "webauthn-polyfills" e importalo nel contesto. Tieni presente che, se PublicKeyCredential è supportato, lo è anche PublicKeyCredential.getClientCapabilities.

import 'webauthn-polyfills';

Easy JSON Serialization Fallback

Installa sempre "webauthn-polyfills" e importalo nel contesto. Tieni presente che, se PublicKeyCredential è supportato, lo sono anche PublicKeyCredential.parseRequestOptionsFromJSON e PublicKeyCredential.prototype.toJSON.

import 'webauthn-polyfills';