本指南著重於差異,詳細說明如何為已登入的使用者實作升級驗證或重新驗證,再讓他們執行敏感的帳戶變更 (例如更新密碼、財務轉帳)。
Delta Flow 架構
與一般驗證不同,密碼金鑰重新驗證會嚴格限制密碼金鑰對話方塊提示,只顯示已登入使用者預先註冊的憑證,防止在有效工作階段期間發生帳戶混淆或密碼金鑰假冒。
伺服器端
選項生成差異
建立端點,專門為已知的活躍使用者填入允許的憑證參數:
限制憑證:在 allowCredentials 選項陣列中填入特定 PublicKeyCredentialDescriptor 記錄,對應登入使用者的所有已註冊憑證 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) 完全相符。如果傳回的是其他使用者的有效密碼金鑰,則必須立即拒絕驗證。
用戶端流程差異
應用程式會根據交易 UI,從兩種重新驗證介面中選擇:
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';
簡易 JSON 序列化備用方案
請務必安裝「webauthn-polyfills」,並將其匯入結構定義。請注意,只要支援 PublicKeyCredential,系統也會支援 PublicKeyCredential.parseRequestOptionsFromJSON 和 PublicKeyCredential.prototype.toJSON。
import 'webauthn-polyfills';