通行金鑰提供基於 Web 驗證 API (WebAuthn) 和 FIDO2 標準的現代化、防網路釣魚的驗證方法。 它們是密碼的安全替代方案,使用公鑰加密和基於設備的身份驗證。 本文說明如何設定 ASP.NET Core 應用程式,以使用通行金鑰來驗證使用者。
如需新和現有 Blazor Web Apps 的特定指引,請參閱閱讀本文後在 ASP.NET Core Blazor Web App中實作通行金鑰 。
什麼是通行金鑰?
通行密鑰是使用加密密鑰組的密碼的替代品。 私鑰安全地存儲在用戶的設備上,例如硬件安全模塊、平台身份驗證器(例如:Windows Hello、Touch ID、Face ID)或密碼管理器中,而公開密鑰則由 Web 應用程序存儲。 在身份驗證期間,用戶證明擁有私鑰,而無需私鑰離開他們的設備。
通行金鑰的主要優點包括:
- 防網路釣魚:通行金鑰綁定到特定網站,不能在虛假網站上使用。
- 無共享秘密:伺服器僅儲存公鑰,消除密碼資料庫外洩的風險。
- 用戶便利性:簡單的生物識別或 PIN 驗證取代了複雜的密碼要求。
- 跨裝置同步:許多通行金鑰提供者會在使用者的裝置之間同步憑證。
如需詳細資訊,請參閱 Web 驗證 API (MDN 檔)。
ASP.NET Core 中的通行金鑰 Identity
ASP.NET Core Identity 包括對通行金鑰註冊和身份驗證的內建支援:
- 與基礎設施無縫 Identity 整合。
- 最常見 WebAuthn 案例的使用者驗證支援。
- 內建於專案範本中 Blazor Web App ,因此只需要開發人員設定。
這很重要
ASP.NET Core Identity 中的通行金鑰實作會刻意限定為驗證案例。 它不是一般用途的 WebAuthn 程式庫。 需要完整 WebAuthn 功能的開發人員應考慮提供全面協定支援的社群函式庫。
支援的案例
ASP.NET Core Identity 通行金鑰實作支援下列主要案例:
- 將通行金鑰新增至現有帳戶:擁有密碼型帳戶的使用者可以註冊通行金鑰作為額外的驗證方法。
- 無密碼帳戶創建: 用戶可以通過在帳戶創建時註冊通行密鑰來創建無需密碼的帳戶。
- 無密碼登入:使用者可以僅使用通行金鑰進行身份驗證,而無需輸入密碼。
局限性
目前的實作有下列限制:
- 範圍為 ASP.NET 核心 Identity:API專為驗證案例所 Identity 設計。
- 沒有預設證明驗證:根據預設,實作不會驗證證明陳述式。
- 範本支援:只有 Blazor Web App 範本包含通行金鑰支援。
- 沒有內建 2FA 支援:通行金鑰被視為主要驗證因素,而不是第二個因素。
核心概念
兩個基本流程支撐通行金鑰操作:證明和斷言。
認證(註冊)
證明 是建立和註冊新通行金鑰的程式。 在證明期間,伺服器會產生驗證器必須包含在傳回認證中的唯一挑戰。 驗證器會建立新的金鑰組,並傳回公開金鑰以及證明金鑰來源的證明資料。 然後,伺服器會驗證此證明並儲存公開金鑰以供將來進行驗證嘗試。
宣告 (驗證)
宣告 是使用現有通行金鑰進行驗證的程式。 伺服器會產生唯一的挑戰,驗證器使用私密金鑰簽署。 驗證器將此已簽署的斷言傳回給伺服器,伺服器使用先前儲存的公開金鑰驗證簽章。 如果簽章有效,則會驗證使用者。
先決條件
- .NET SDK (.NET 10 或更新版本)
- 支援 WebAuthn 的現代 Web 瀏覽器。
- 具有平台驗證器的裝置,例如 Windows Hello 或 Apple 安全記憶體保護區,或安全性金鑰。
安全性考慮
在 ASP.NET Core Identity中實作通行金鑰時,請確定應用程式符合本節所述的安全性需求。
主機標頭驗證
當未明確設定時 ServerDomain ,實作會從主機標頭推斷出信賴憑證者識別碼。 託管環境必須驗證主機標頭以防止憑證範圍攻擊,這涉及使用入侵或被盜的使用者憑證(使用者名稱、密碼、權杖)來獲得未經授權的存取。
風險降低:在 中明確設定ServerDomainIdentityPasskeyOptions或確保裝載環境 (Kestrel、IIS、反向 Proxy) 驗證主機標頭。 如需設定詳細資料,請參閱託管平台的檔案。
子網域安全性
ASP.NET Core 的通行金鑰實作透過配置選項處理 ServerDomain 子網域安全性。 如果未明確指定,實 ServerDomain 作會使用主機標頭來判斷網域。 這表示 註冊通行金鑰的頁面會控制 該認證的網域。
例如:
- 如果通行金鑰是在 上
app.contoso.com註冊的,它也適用於*.app.contoso.com。 - 如果在 上
contoso.com註冊,它也適用於*.contoso.com。 - 瀏覽器強制通行金鑰只能在註冊的網域(和子網域)上使用。
需求:需要嚴格網域控制的應用程式應明確設定 ServerDomain ,而不是依賴主機標頭。 請勿在範圍內的任何子網域 ServerDomain 上提供不受信任的內容。 如果您無法保證這一點,請實作 自訂來源驗證 ,以將通行金鑰的使用限制為特定來源。
HTTPS 需求
所有通行金鑰操作都需要 HTTPS。 實作會將驗證資料儲存在加密和簽署的 Cookie 中,這些 Cookie 可能會透過未加密的連線攔截。
需求:在生產環境中一律使用 HTTPS。 設定 HTTP 嚴格傳輸安全通訊協定 (HSTS) 以防止通訊協定降級攻擊。
帳戶復原
帳戶恢復主要是允許通行密鑰作為唯一身份驗證機制的應用程序所關注的問題。 預設 Blazor Web App 專案範本已要求使用者在建立帳戶時設定備份驗證方法(密碼或外部提供者),因此帳戶恢復是透過這些現有機制來處理的。
建議:
對於實作僅通行金鑰驗證的應用程式,請考慮:
- 建立帳戶期間產生的復原碼。
- 以電子郵件為基礎的復原流程。
- 強制註冊多個通行金鑰。
- 監控 IsBackedUp 旗標 UserPasskeyInfo 以提示使用者新增其他認證。
系統管理控制
當發現驗證器模型具有安全性弱點時,您可能需要撤銷受影響的認證。 實作會儲存每個認證的完整證明物件,包括驗證器證明 GUID (AAGUID),這是指出金鑰類型的 128 位識別碼。
實作:從儲存的證明物件擷取 AAGUID,與已知遭入侵的模型進行比較,並撤銷受影響的認證。 AAGUID 可靠性取決於您的應用程式是否驗證證明陳述式。 若要掛鉤自訂證明陳述式驗證邏輯,請參閱 自訂證明陳述式驗證。 第三方程式庫可用於證明驗證,例如 通行金鑰 - FIDO2 .NET 程式庫 (WebAuthn) (passwordless-lib/fido2-net-lib GitHub 存放庫)†。
警告
†第三方程式庫 (包括 passwordless-lib/fido2-net-lib) 不屬於 Microsoft 擁有或維護,也不在任何Microsoft支援合約或授權範圍內。 採用第三方程式庫時要小心,尤其是安全功能。 確認函式庫遵循官方規範並採用安全最佳實踐。 保持程式庫的版本最新,以取得最新的錯誤修正。
資源限制
為防止資料庫耗盡攻擊,App 應對通行金鑰註冊強制執行限制,例如:
- 每個使用者帳戶的通行金鑰數目上限。
- 通行金鑰顯示名稱的長度上限。
Blazor Web App範本預設會在應用程式層級強制執行這些限制。 如需範例,請參閱專案範本中的Razor下列Blazor Web App元件:
備註
通常,指向 .NET 參考來源的文件連結會載入存放庫的預設分支,這代表 .NET 下一版本的最新開發進度。 若要選取特定發行版本的標籤,請使用「切換分支或標籤」下拉式清單。 如需詳細資訊,請參閱如何選取 ASP.NET Core 原始程式碼 (dotnet/AspNetCore.Docs #26205) 的版本標籤。
設定通行金鑰選項
ASP.NET Core Identity 提供了各種選項來透過類別配置 IdentityPasskeyOptions 通行金鑰行為,其中包括:
- AuthenticatorTimeout:取得或設定瀏覽器應等待驗證器提供通行金鑰 TimeSpan的時間。 此選項適用於建立新通行金鑰和要求現有通行金鑰。 此選項被視為對瀏覽器的提示,瀏覽器可能會忽略該選項。 預設值為 5 分鐘。
- ChallengeSize:取得或設定在證明和宣告期間傳送至用戶端的挑戰大小 (以位元組為單位)。 此選項適用於建立新通行金鑰和要求現有通行金鑰。 預設值為 32 個位元組。
-
ServerDomain:取得或設定伺服器的有效信賴憑證者識別碼 (網域)。 這應該是唯一的,並將用作伺服器的身份。 此選項適用於建立新通行金鑰和要求現有通行金鑰。 如果 ,這是預設值,則
null會使用伺服器的來源。 如需詳細資訊,請參閱 信賴憑證者識別碼 RP 識別碼。
設定範例:
builder.Services.Configure<IdentityPasskeyOptions>(options =>
{
options.ServerDomain = "contoso.com";
options.AuthenticatorTimeout = TimeSpan.FromMinutes(3);
options.ChallengeSize = 64;
});
如需組態選項的完整清單,請參閱 IdentityPasskeyOptions。 如需最新的瀏覽器預設值,請參閱 W3C WebAuthn 規格。
備註
.NET 參考來源的檔連結通常會載入存放庫的預設分支,這代表下一個 .NET 預覽版的目前開發。 若要選取特定發行版本的標籤,請使用「切換分支或標籤」下拉式清單。 如需詳細資訊,請參閱如何選取 ASP.NET 核心原始程式碼的版本標籤 (dotnet/AspNetCore.Docs #26205)。
自訂證明陳述式驗證
根據預設,ASP.NET Core Identity 不會驗證證明陳述式。 這適用於大多數消費者身份驗證場景。 如果您的應用程式需要驗證驗證器屬性,或想要禁止使用特定驗證器 (例如,在需要更高層級安全性的企業環境中),您可以實作自訂證明驗證:
builder.Services.Configure<IdentityPasskeyOptions>(options =>
{
options.VerifyAttestationStatement = async (context) =>
{
// Custom attestation validation logic
// Return 'true' if the attestation is valid
// Return 'false' if the attestation is invalid
return true;
};
});
警告
證明驗證很複雜,需要維護驗證器憑證的信任存放區。 只有在應用程式需要驗證特定驗證器屬性時,才實作自訂驗證。
自訂來源驗證
預設來源驗證允許來自子網域的請求,而不允許跨來源 iframe。 若要自訂此行為:
builder.Services.Configure<IdentityPasskeyOptions>(options =>
{
options.ValidateOrigin = async (context) =>
{
// Custom origin validation logic
// Access the origin via 'context.Origin'
// Access the HTTP context via 'context.HttpContext'
// Return 'true' if the origin is valid
// Return 'false' if the origin is invalid
return true;
};
});
註冊流程
本節將逐步介紹通行金鑰註冊程序的每個步驟,說明 ASP.NET Core Identity 如何協助建立和儲存通行金鑰憑證。
sequenceDiagram
participant Authenticator
participant User
participant Browser
participant Server
User->>Browser: Click "Add passkey"
Browser->>Server: Request creation options
Server->>Browser: Return creation options
Browser->>Authenticator: Request new credential
Authenticator->>User: Verify identity (biometric/PIN)
User->>Authenticator: Approve
Authenticator->>Browser: Return credential
Browser->>Server: Submit credential
Server->>Server: Verify and store
Server->>Browser: Registration complete
Browser->>User: Success message
第 1 步:啟動註冊
當使用者決定將通行金鑰新增至其帳戶時,註冊程序就開始了。 這通常透過應用程式使用者介面中的按鈕或連結發生。 選取時,此元素會觸發 JavaScript 程式碼來協調註冊流程。
用戶端實作在應用程式之間有很大差異。 在範本中 Blazor Web App ,您可以在中找到完整的範例 PasskeySubmit.razor.js,其中顯示自訂 Web 元件如何處理註冊起始,並管理後續的 WebAuthn API 呼叫。
步驟 2:請求建立選項
啟動註冊後,瀏覽器必須從伺服器取得建立選項。 這些選項會告訴瀏覽器要建立的認證類型,並包含重要的安全性參數,例如必須簽署的挑戰。
從瀏覽器的角度來看,這一步涉及向伺服器發出HTTP請求:
async function createCredential(headers, signal) {
// Step 2: Request creation options from the server
const optionsResponse =
await fetchWithErrorHandling('/Account/PasskeyCreationOptions',
{
method: 'POST',
headers,
signal,
});
const optionsJson = await optionsResponse.json();
const options = PublicKeyCredential.parseCreationOptionsFromJSON(optionsJson);
return await navigator.credentials.create({ publicKey: options, signal });
}
應用程式應該定義產生下列選項的端點:
app.MapPost("/Account/PasskeyCreationOptions", async (
HttpContext context,
UserManager<ApplicationUser> userManager,
SignInManager<ApplicationUser> signInManager) =>
{
var user = await userManager.GetUserAsync(context.User);
if (user is null)
{
return Results.NotFound();
}
var userId = await userManager.GetUserIdAsync(user);
var userName = await userManager.GetUserNameAsync(user) ?? "User";
var optionsJson = await signInManager.MakePasskeyCreationOptionsAsync(new()
{
Id = userId,
Name = userName,
DisplayName = userName
});
return TypedResults.Content(optionsJson, contentType: "application/json");
});
該 MakePasskeyCreationOptionsAsync 方法是此過程的核心。 該方法接受描述正在為其建立通行金鑰的使用者的 a PasskeyUserEntity 。 此實體包含使用者的 ID、使用者名稱 (通常是電子郵件地址) 和人類可讀的顯示名稱。 此方法會傳回符合 WebAuthn PublicKeyCredentialCreationOptions 結構描述的 JSON 字串,瀏覽器會在下一個步驟中使用該結構描述。 在幕後,此方法也會將臨時狀態儲存在身份驗證 cookie 中,以確保瀏覽器的回應與這些特定選項相對應。
步驟 3:伺服器產生選項
執行時 MakePasskeyCreationOptionsAsync ,它會使用應用程式的 IdentityPasskeyOptions 設定來決定憑證建立的特定參數。 這些選項控制通行金鑰建立過程的各個方面。
您可以在應用程式啟動期間自訂這些選項。 例如:
builder.Services.Configure<IdentityPasskeyOptions>(options =>
{
options.ServerDomain = "contoso.com";
options.AuthenticatorTimeout = TimeSpan.FromMinutes(3);
options.UserVerificationRequirement = "required";
options.ResidentKeyRequirement = "preferred";
});
此 UserVerificationRequirement 選項會決定驗證器是否必須驗證使用者的身分 (透過生物特徵辨識或 PIN 方法),同時 ResidentKeyRequirement 指出認證是否應該可探索,允許驗證,而不需要先提供使用者名稱。 如需詳細資訊,請參閱IdentityPasskeyOptions。
步驟 4:用戶端要求認證
使用可用的建立選項,用戶端 JavaScript 會將選項傳遞至 WebAuthn API 以建立新的認證:
async function createCredential(headers, signal) {
// Step 4: Parse the options and request a new credential from the authenticator
const optionsResponse =
await fetchWithErrorHandling('/Account/PasskeyCreationOptions',
{
method: 'POST',
headers,
signal,
});
const optionsJson = await optionsResponse.json();
const options = PublicKeyCredential.parseCreationOptionsFromJSON(optionsJson);
return await navigator.credentials.create({ publicKey: options, signal });
}
此 parseCreationOptionsFromJSON 函式會將 JSON 回應轉換為 WebAuthn API 預期的格式,並 navigator.credentials.create() 使用驗證器啟動認證建立程式。
步驟 5:驗證器互動
此時,瀏覽器會與驗證器通訊以建立認證。 身份驗證器會提示使用者進行驗證,這可能涉及掃描指紋、輸入 PIN 碼或使用臉部辨識。 此互動完全由瀏覽器和身份驗證器處理,不需要應用程式程式碼。 使用者體驗因身份驗證器的類型和平台的功能而異。
步驟 6:憑證提交
驗證器建立憑證後,瀏覽器必須將憑證傳回伺服器進行驗證和儲存。 在提交之前,認證必須序列化為JSON:
async function createCredential(headers, signal) {
// Step 6: The credential is returned from navigator.credentials.create()
// and is serialized to JSON for submission to the server
const optionsResponse =
await fetchWithErrorHandling('/Account/PasskeyCreationOptions',
{
method: 'POST',
headers,
signal,
});
const optionsJson = await optionsResponse.json();
const options = PublicKeyCredential.parseCreationOptionsFromJSON(optionsJson);
return await navigator.credentials.create({ publicKey: options, signal });
}
在範本中 Blazor Web App ,傳回的憑證會自動序列化並透過表單提交,但確切的提交機制因應用程式而異。
第七步:伺服器驗證與儲存
當伺服器收到憑證時,它必須驗證其有效性並儲存公鑰以供將來進行身份驗證。 這就是 ASP.NET Core Identity的通行金鑰 API 變得至關重要的地方。
此 PerformPasskeyAttestationAsync 方法會驗證用戶端的證明回應。 這個全面的驗證過程:
- 驗證認證類型是否符合預期。
- 驗證用戶端資料 JSON,包括來源和挑戰。
- 檢查驗證器資料旗標,以取得使用者目前狀態和驗證
- 擷取並驗證公開金鑰。
如果所有檢查都通過,則方法會傳回包含已驗證通行金鑰資訊的 。PasskeyAttestationResult
驗證證明之後,應用程式會用來 AddOrUpdatePasskeyAsync 將通行金鑰儲存在資料庫中:
var attestationResult =
await signInManager.PerformPasskeyAttestationAsync(credentialJson);
if (!attestationResult.Succeeded)
{
return Results.BadRequest($"Error: {attestationResult.Failure.Message}");
}
var addResult =
await userManager.AddOrUpdatePasskeyAsync(user, attestationResult.Passkey);
if (!addResult.Succeeded)
{
return Results.BadRequest("Failed to store passkey");
}
儲存 UserPasskeyInfo 的內容包含未來驗證所需的所有資訊,包括憑證 ID、公開金鑰、用於重播保護的簽章計數器,以及指示通行金鑰是否已備份或符合備份資格的旗標。
步驟 8:註冊後任務
成功註冊通行金鑰後,應用程式通常會執行額外的任務來改善使用者體驗。 常見的模式是提示使用者為其通行金鑰提供易記名稱,以便更容易在多個憑證之間進行識別。 屬性 UserPasskeyInfo.Name 會儲存這個使用者易記的名稱,可以使用相同的 AddOrUpdatePasskeyAsync 方法進行更新:
passkey.Name = "My iPhone";
await userManager.AddOrUpdatePasskeyAsync(user, passkey);
驗證流程
本節說明使用者如何使用其通行金鑰進行身份驗證,從啟動登入過程到建立經過身份驗證的工作階段。
sequenceDiagram
participant Authenticator
participant User
participant Browser
participant Server
User->>Browser: Click "Sign in with passkey"
Browser->>Server: Request authentication options
Server->>Browser: Return authentication options
Browser->>Authenticator: Request assertion
Authenticator->>User: Verify identity
User->>Authenticator: Approve
Authenticator->>Browser: Return signed assertion
Browser->>Server: Submit assertion
Server->>Server: Verify signature
Server->>Browser: Authentication complete
Browser->>User: Redirect to app
步驟 1:啟動驗證
使用者通常透過登入頁面上的專用按鈕或連結啟動密鑰身份驗證。 某些應用程式也支援條件式 UI,其中通行金鑰會在使用者名稱欄位中顯示為自動填入建議。 起始方法會觸發管理驗證流程的 JavaScript 程式碼,類似於註冊程式。
步驟 2:要求驗證選項
瀏覽器會向伺服器請求驗證選項,以開始驗證程式。 這些選項包括可接受的認證清單和要簽署的新挑戰:
async function requestCredential(email, mediation, headers, signal) {
// Step 2: Request authentication options from the server
const optionsResponse =
await fetchWithErrorHandling(`/Account/PasskeyRequestOptions?username=${email}`,
{
method: 'POST',
headers,
signal,
});
const optionsJson = await optionsResponse.json();
const options = PublicKeyCredential.parseRequestOptionsFromJSON(optionsJson);
return await navigator.credentials.get({ publicKey: options, mediation, signal });
}
此 MakePasskeyRequestOptionsAsync 方法會產生這些選項。 當您提供特定使用者時,它只會在允許清單中包含該使用者的認證。 在沒有使用者的情況下呼叫時,它會產生適合條件式 UI 或無使用者名稱驗證的選項:
app.MapPost("/Account/PasskeyRequestOptions", async (
SignInManager<ApplicationUser> signInManager,
string? username) =>
{
var user = string.IsNullOrEmpty(username)
? null
: await userManager.FindByNameAsync(username);
var optionsJson = await signInManager.MakePasskeyRequestOptionsAsync(user);
return TypedResults.Content(optionsJson, contentType: "application/json");
});
步驟 3:伺服器產生選項
伺服器會使用註冊期間使用的相同 IdentityPasskeyOptions 組態來產生鑑別選項。 必須 ServerDomain 與最初註冊通行金鑰的網域相符,否則驗證失敗。 會 UserVerificationRequirement 判斷驗證器是否必須在驗證期間驗證使用者的身分識別。
步驟 4:用戶端要求斷言
用戶端JavaScript會將驗證選項傳遞至WebAuthn API,以要求驗證器判斷提示:
async function requestCredential(email, mediation, headers, signal) {
// Step 4: Parse the options and request an assertion from the authenticator
const optionsResponse =
await fetchWithErrorHandling(`/Account/PasskeyRequestOptions?username=${email}`,
{
method: 'POST',
headers,
signal,
});
const optionsJson = await optionsResponse.json();
const options = PublicKeyCredential.parseRequestOptionsFromJSON(optionsJson);
return await navigator.credentials.get({ publicKey: options, mediation, signal });
}
呼叫會 navigator.credentials.get() 啟動驗證器的驗證程式,提示使用者進行驗證。
步驟 5:驗證器驗證
驗證器會驗證使用者的身分,並使用私密金鑰簽署挑戰。 此過程完全由瀏覽器和身份驗證器處理,類似於註冊期間的驗證步驟。 使用者體驗取決於身份驗證器類型,可能涉及生物辨識驗證或 PIN 輸入。
步驟6:斷言提交
驗證器建立已簽署的宣告之後,瀏覽器會將其序列化為 JSON 並提交至伺服器:
async function requestCredential(email, mediation, headers, signal) {
// Step 6: The assertion is returned from navigator.credentials.get()
// and is serialized to JSON for submission to the server
const optionsResponse =
await fetchWithErrorHandling(`/Account/PasskeyRequestOptions?username=${email}`,
{
method: 'POST',
headers,
signal,
});
const optionsJson = await optionsResponse.json();
const options = PublicKeyCredential.parseRequestOptionsFromJSON(optionsJson);
return await navigator.credentials.get({ publicKey: options, mediation, signal });
}
提交機制因應用程式而異,但通常涉及表單提交或 API 呼叫。
第 7 步:伺服器驗證
伺服器會驗證宣告以驗證使用者。 ASP.NET Core Identity 提供方法 PasskeySignInAsync ,可在單一呼叫中執行完整的驗證流程:
var result = await signInManager.PasskeySignInAsync(credentialJson);
if (result.Succeeded)
{
return Results.Ok("Authentication successful");
}
return Results.Unauthorized();
此 PasskeySignInAsync 方法會在內部呼叫 PerformPasskeyAssertionAsync :
- 使用儲存的公開金鑰驗證宣告簽章。
- 驗證挑戰是否與最初發送的挑戰相符。
- 檢查驗證器旗標以取得使用者目前狀態和驗證。
- 更新簽章計數器以防止重播攻擊。
如果所有檢查都通過,方法會登入使用者並傳回 SignInResult 指示成功的訊息。
對於需要更多控制的案例,您可以使用 直接來 PerformPasskeyAssertionAsync 驗證判斷提示,而不需要立即登入使用者:
- PerformPasskeyAssertionAsync 傳回包含 PasskeyAssertionResult 已驗證使用者和更新的通行金鑰資訊。
- 因為通行金鑰的登入計數和驗證器旗標可能自上次宣告以來已變更,而且呼叫時不會自動儲存PerformPasskeyAssertionAsyncUserManager<TUser>.AddOrUpdatePasskeyAsync更新的通行金鑰,並傳回 PasskeyAssertionResult.
第 8 步:會話建立
驗證成功後,ASP.NET Core Identity 會為使用者建立經過驗證的工作階段。 該 PasskeySignInAsync 方法會自動處理此問題,並建立必要的身份驗證 cookie 和聲明。 然後,該應用程式將使用者重新導向到受保護的資源或顯示個人化內容。
減輕 PublicKeyCredential.toJSON 錯誤 (TypeError: Illegal invocation)
該 PublicKeyCredential.toJSON 方法 傳回 PublicKeyCredential 的 JSON 表示。 密碼管理員在應用程式嘗試註冊或驗證使用者時,透過呼叫PublicKeyCredential來序列化JSON.stringify時,會叫用此方法。
某些密碼管理器無法正確實現 PublicKeyCredential.toJSON 方法,這對於序列化通行金鑰憑證時 JSON.stringify 的運作是必需的。 根據專案範本向 Blazor Web App 應用程式註冊或驗證使用者時,某些密碼管理員在嘗試新增通行金鑰時會擲回下列錯誤:
Error: Could not add a passkey: Illegal invocation
在您選擇的密碼管理器更新以正確實施該 PublicKeyCredential.toJSON 方法之前,請對應用程序進行以下更改。 下列程式碼會手動 JSON 序列化 PublicKeyCredential.
在檔案中 Components/Account/Shared/PasskeySubmit.razor.js ,找出 passkey-submit 自訂元素定義程式碼區塊:
customElements.define('passkey-submit', class extends HTMLElement {
...
});
將下列 convertToBase64 函式新增至程式碼區塊:
convertToBase64(o) {
if (!o) {
return undefined;
}
// Normalize Array to Uint8Array
if (Array.isArray(o)) {
o = Uint8Array.from(o);
}
// Normalize ArrayBuffer to Uint8Array
if (o instanceof ArrayBuffer) {
o = new Uint8Array(o);
}
// Convert Uint8Array to base64
if (o instanceof Uint8Array) {
let str = '';
for (let i = 0; i < o.byteLength; i++) {
str += String.fromCharCode(o[i]);
}
o = window.btoa(str);
}
if (typeof o !== 'string') {
throw new Error("Could not convert to base64 string");
}
// Convert base64 to base64url
o = o.replace(/\+/g, "-").replace(/\//g, "_").replace(/=*$/g, "");
return o;
}
在程式碼區塊的函式中 obtainAndSubmitCredential ,找出使用使用者認證呼叫 JSON.stringify 的行,並移除該行:
- const credentialJson = JSON.stringify(credential);
將前一行取代為下列程式碼:
const credentialJson = JSON.stringify({
authenticatorAttachment: credential.authenticatorAttachment,
clientExtensionResults: credential.getClientExtensionResults(),
id: credential.id,
rawId: this.convertToBase64(credential.rawId),
response: {
attestationObject: this.convertToBase64(credential.response.attestationObject),
authenticatorData: this.convertToBase64(credential.response.authenticatorData ??
credential.response.getAuthenticatorData?.() ?? undefined),
clientDataJSON: this.convertToBase64(credential.response.clientDataJSON),
publicKey: this.convertToBase64(credential.response.getPublicKey?.() ?? undefined),
publicKeyAlgorithm: credential.response.getPublicKeyAlgorithm?.() ?? undefined,
transports: credential.response.getTransports?.() ?? undefined,
signature: this.convertToBase64(credential.response.signature),
userHandle: this.convertToBase64(credential.response.userHandle),
},
type: credential.type,
});
只有在更新密碼管理器以正確實施 PublicKeyCredential.toJSON 該方法之前,才需要上述因應措施。 我們建議您追蹤密碼管理器的版本說明,並在更新密碼管理器後恢復先前的變更。