Blazor WASM如何调起设备原生Touch ID/Face ID生物识别弹窗
可行性结论
完全可以实现。你需要的「仅调起系统原生生物识别弹窗、后续认证逻辑自主处理、无第三方服务依赖」的需求,完全可以通过浏览器原生标准能力实现,不需要接入任何重型第三方认证库。
目前网上多数生物认证相关库,本质是对W3C标准Web Authentication API(WebAuthn)做了封装,再绑定了自家的服务端校验逻辑。如果不需要第三方提供的认证服务,完全可以直接在Blazor WASM中调用原生WebAuthn API,覆盖所有你提到的运行环境:
- iOS 14+ 全量浏览器(Safari、Chrome、Edge等,iOS 15+完整支持Face ID、Touch ID调起)
- Android 7+ 支持生物识别的主流浏览器(Chrome、Edge、火狐等)
- 桌面端Chrome 67+、Edge 79+、Safari 13+、Firefox 60+(支持Windows Hello、macOS Touch ID系统验证弹窗)
所有环境下调起的都是系统级原生验证弹窗,没有中间自定义弹窗层,体验和原生App生物识别完全一致。
具体实现路径
整个实现不需要引入任何第三方NuGet包或JS依赖,总代码量不到200行,完全自主可控。
- 第一步:基础环境检测
调用生物识别前先判断当前设备、浏览器是否支持平台级生物认证,避免不兼容环境报错,核心判断逻辑是检测浏览器是否暴露PublicKeyCredential接口,以及是否配置了可用的系统生物识别器。 - 第二步:封装轻量JS互操作层
Blazor WASM无法直接调用浏览器原生API,只需要写一个极简的JS模块做桥接即可,示例代码如下:
首先在wwwroot/js目录下新建webAuthn.js文件:
然后在Blazor组件/Razor页面中调用即可,示例代码:export async function checkBiometricSupport() { if (!window.PublicKeyCredential) { return { supported: false, reason: "当前浏览器不支持生物认证能力" }; } const platformAuthAvailable = await PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable(); return { supported: platformAuthAvailable, reason: platformAuthAvailable ? "" : "当前设备未配置系统生物识别(Touch ID/Face ID/Windows Hello等)" }; } export async function triggerBiometricPrompt(challenge, credentialId = null) { const publicKeyConfig = { challenge: Uint8Array.from(challenge, c => c.charCodeAt(0)), rpId: window.location.hostname, userVerification: "required", // 强制要求生物验证通过,不允许跳过 timeout: 60000, allowCredentials: credentialId ? [{ id: Uint8Array.from(atob(credentialId), c => c.charCodeAt(0)), type: "public-key", transports: ["internal"] }] : [] }; try { // 该行代码执行后会直接调起系统原生生物识别弹窗 const authResult = await navigator.credentials.get({ publicKey: publicKeyConfig }); return { success: true, authData: Array.from(new Uint8Array(authResult.response.authenticatorData)), clientData: Array.from(new Uint8Array(authResult.response.clientDataJSON)), signature: Array.from(new Uint8Array(authResult.response.signature)) }; } catch (err) { return { success: false, error: err.message || "生物识别验证失败" }; } } // 仅做本地验证时,用来生成本地存储的测试凭证 export async function createLocalTestCredential() { const createConfig = { publicKey: { rp: { id: window.location.hostname, name: window.location.hostname }, user: { id: new Uint8Array(16), name: "local_user", displayName: "Local User" }, challenge: new Uint8Array(32), pubKeyCredParams: [{ type: "public-key", alg: -7 }], authenticatorSelection: { authenticatorAttachment: "platform", userVerification: "required" }, timeout: 60000 } }; const cred = await navigator.credentials.create(createConfig); return btoa(String.fromCharCode(...new Uint8Array(cred.rawId))); }@inject IJSRuntime JS @inject IYourOwnAuthService ExistingAuthService // 替换成你自己现有的认证服务 <button @onclick="DoBiometricAuth" disabled="@!_isSupportBiometric" class="btn btn-primary"> 生物识别验证 </button> @code { private IJSObjectReference? _webAuthnModule; private bool _isSupportBiometric; private string? _localCredentialId; private const string LocalCredStorageKey = "local_biometric_cred_id"; protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { _webAuthnModule = await JS.InvokeAsync<IJSObjectReference>("import", "./js/webAuthn.js"); var supportRes = await _webAuthnModule.InvokeAsync<SupportCheckResult>("checkBiometricSupport"); _isSupportBiometric = supportRes.Supported; // 仅做本地验证时,检查是否已有本地测试凭证,没有则创建 if (_isSupportBiometric) { _localCredentialId = await JS.InvokeAsync<string?>("localStorage.getItem", LocalCredStorageKey); if (string.IsNullOrEmpty(_localCredentialId)) { _localCredentialId = await _webAuthnModule.InvokeAsync<string>("createLocalTestCredential"); await JS.InvokeVoidAsync("localStorage.setItem", LocalCredStorageKey, _localCredentialId); } } StateHasChanged(); } } private async Task DoBiometricAuth() { if (!_isSupportBiometric || _webAuthnModule == null) return; // 从你自己现有认证服务生成随机挑战码,用于防重放 var challenge = await ExistingAuthService.GenerateAuthChallenge(); var authRes = await _webAuthnModule.InvokeAsync<BiometricAuthResult>( "triggerBiometricPrompt", challenge, _localCredentialId ); if (authRes.Success) { // 生物识别通过,直接走你自己现有的认证逻辑即可,不需要依赖任何第三方服务 await ExistingAuthService.CompleteAuthProcess(authRes); } else { // 自行处理识别失败场景:用户取消、识别不匹配、超时等 Console.WriteLine($"验证失败:{authRes.Error}"); } } // 接口返回结构定义 public class SupportCheckResult { public bool Supported { get; set; } public string Reason { get; set; } = string.Empty; } public class BiometricAuthResult { public bool Success { get; set; } public byte[] AuthData { get; set; } = []; public byte[] ClientData { get; set; } = []; public byte[] Signature { get; set; } = []; public string Error { get; set; } = string.Empty; } } - 第三步:对接自有认证逻辑
拿到生物识别通过的结果后,完全可以根据自己的需求对接现有认证体系,没有任何限制:- 如果只需要确认当前操作是设备本人发起,直接在收到
Success=true的结果后放行原有业务逻辑即可,不需要额外校验 - 如果需要做高强度的防篡改校验,可以把返回的签名、认证数据、挑战码回传给你自己的服务端,用你自己存储的公钥做签名校验,全程不需要调用第三方接口
- 可以直接复用现有登录、JWT签发、权限校验等逻辑,不需要重构原有认证体系
- 如果只需要确认当前操作是设备本人发起,直接在收到
注意事项
- WebAuthn标准要求生产环境必须使用HTTPS协议,本地调试localhost环境不受此限制
- 上述代码中创建的本地测试凭证仅存储在用户设备本地,不会上传到任何服务器,完全符合隐私要求
- 如果后续需要扩展免密登录、设备绑定等能力,只需要在现有代码基础上补充WebAuthn凭证注册、服务端公钥存储逻辑即可,不需要重构现有代码
- 不需要申请任何第三方服务权限、密钥,没有额外成本
内容的提问来源于stack exchange,提问作者Hikari
相关产品推荐
相关产品推荐

