通行密钥重新身份验证

Web 功能: Web 身份验证

本指南重点介绍如何为已登录用户实现升级身份验证或重新验证,以便在他们执行敏感账号更改(例如密码更新、财务转账)之前进行验证。

Delta Flow 架构

与常规身份验证不同,通行密钥重新身份验证会将通行密钥对话框提示严格限制为已登录用户的预注册凭据,以防止在活跃会话期间出现账号混淆或通行密钥欺骗。

服务器端

选项生成增量

创建一个端点,专门为活跃的已知用户填充允许的凭据参数:

限制凭证:使用特定的 PublicKeyCredentialDescriptor 记录填充 allowCredentials 选项数组,这些记录会映射已登录用户的所有已注册凭证 ID。如果留空或省略此字段,则会回退到可检测到的凭据,从而违反会话安全。

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

验证端点增量

验证客户端返回的断言:

验证账号所有权:验证端点必须明确验证客户端返回的经过身份验证的凭证 ID 是否解析为存储的凭证记录,且该记录关联的用户 ID 与当前登录的用户 (storedCredential.passkeyUserId === req.user.id) 严格匹配。如果返回的是其他用户的有效通行密钥,则必须立即拒绝身份验证。

客户端流程差异

应用可根据交易界面从两个重新身份验证界面中进行选择:

A. 按钮流程(无输入字段)

当用户按下“验证身份”或“继续交易”按钮时,触发重新身份验证。

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

后备策略

通行密钥功能检测回退

始终安装“webauthn-polyfills”并将其导入到上下文中。 请注意,只要支持 PublicKeyCredential,就支持 PublicKeyCredential.getClientCapabilities。

import 'webauthn-polyfills';

Easy JSON Serialization Fallback

始终安装“webauthn-polyfills”并将其导入到上下文中。 请注意,只要支持 PublicKeyCredential,就同样支持 PublicKeyCredential.parseRequestOptionsFromJSON 和 PublicKeyCredential.prototype.toJSON。

import 'webauthn-polyfills';