Electron 42.1.0 是一个以修复为主的补丁版本,但其中一项改动对依赖生物识别认证的桌面应用影响不小——macOS Touch ID 在 WebAuthn 流程中的提示异常被修复了。如果你正在用 Electron 构建需要本地身份验证的桌面产品(比如加密钱包、企业内部工具、密码管理器),这个版本值得立刻跟进。
修复了什么
此次版本的核心修复是:macOS Touch ID WebAuthn 提示缺失问题。此前在某些场景下,当应用通过 WebAuthn API 请求生物识别认证时,系统级的 Touch ID 弹窗不会正常出现,导致认证流程卡住或直接失败。这对用户体验和安全性都是硬伤——用户无法完成身份验证,应用只能回退到备用方案或直接报错。
这个问题的根因与 Chromium 层在 macOS 上对 platform authenticator 的调用路径有关。Electron 内嵌的 Chromium 版本在特定条件下未能正确触发 macOS Security Framework 的 Touch ID 交互界面,42.1.0 对这一调用链做了修正。
WebAuthn 在 Electron 中怎么用
WebAuthn(Web Authentication API)是 W3C 标准化的无密码认证协议,支持平台认证器(如 Touch ID、Windows Hello)和漫游认证器(如 YubiKey)。在 Electron 中,渲染进程的 navigator.credentials API 可以直接调用,但有几个细节需要注意:
webPreferences中必须启用相关特性:默认配置下,部分安全 API 可能受限。- 需要 HTTPS 或 localhost:WebAuthn 要求安全上下文(Secure Context),否则
navigator.credentials对象不可用。 - macOS 权限声明:如果打包后的应用要使用 Touch ID,需要在
Info.plist中声明NSFaceIDUsageDescription。
下面是一个最小可运行的 Electron + WebAuthn 示例项目:
项目结构
electron-webauthn-demo/
├── main.js
├── preload.js
├── index.html
├── package.json
package.json
{
"name": "electron-webauthn-demo",
"version": "1.0.0",
"main": "main.js",
"scripts": {
"start": "electron ."
},
"devDependencies": {
"electron": "^42.1.0"
}
}
main.js
const { app, BrowserWindow } = require('electron');
const path = require('path');
function createWindow() {
const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
// 不建议在生产环境打开 nodeIntegration,这里仅作演示
nodeIntegration: false,
contextIsolation: true
}
});
// WebAuthn 要求安全上下文,加载 localhost 或 HTTPS 地址
// 本地开发用 file:// 协议在部分版本下也可工作,但推荐用本地服务器
win.loadFile('index.html');
}
app.whenReady().then(createWindow);
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') app.quit();
});
preload.js
const { contextBridge } = require('electron');
// 暴露安全 API 给渲染进程(演示中直接在 HTML 内调用 navigator.credentials)
contextBridge.exposeInMainWorld('electronDemo', {
platform: process.platform
});
index.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>WebAuthn Touch ID Demo</title>
<style>
body { font-family: -apple-system, sans-serif; padding: 2rem; }
button { padding: 0.6rem 1.2rem; font-size: 1rem; cursor: pointer; }
#log { margin-top: 1rem; white-space: pre-wrap; background: #f5f5f5; padding: 1rem; }
</style>
</head>
<body>
<h2>WebAuthn 注册与验证</h2>
<button id="register">注册(Create Credential)</button>
<button id="authenticate">验证(Get Credential)</button>
<div id="log"></div>
<script>
const log = document.getElementById('log');
// 演示用的挑战值,生产环境应从服务器获取随机 challenge
function randomBuffer(length) {
const buf = new Uint8Array(length);
crypto.getRandomValues(buf);
return buf;
}
let savedCredentialId = null;
document.getElementById('register').addEventListener('click', async () => {
try {
const credential = await navigator.credentials.create({
publicKey: {
challenge: randomBuffer(32),
rp: { name: 'Electron Demo', id: 'localhost' },
user: {
id: randomBuffer(16),
name: 'demo@example.com',
displayName: 'Demo User'
},
pubKeyCredParams: [{ type: 'public-key', alg: -7 }],
authenticatorSelection: {
authenticatorAttachment: 'platform', // 使用 Touch ID / Windows Hello
userVerification: 'required'
},
timeout: 60000
}
});
savedCredentialId = credential.rawId;
log.textContent = '注册成功!Credential ID: ' +
Array.from(new Uint8Array(credential.rawId)).map(b => b.toString(16).padStart(2, '0')).join('');
} catch (e) {
log.textContent = '注册失败: ' + e.message;
}
});
document.getElementById('authenticate').addEventListener('click', async () => {
if (!savedCredentialId) {
log.textContent = '请先注册';
return;
}
try {
const assertion = await navigator.credentials.get({
publicKey: {
challenge: randomBuffer(32),
allowCredentials: [{
type: 'public-key',
id: savedCredentialId,
transports: ['internal']
}],
userVerification: 'required',
timeout: 60000
}
});
log.textContent = '验证成功!签名长度: ' + assertion.signature.byteLength + ' bytes';
} catch (e) {
log.textContent = '验证失败: ' + e.message;
}
});
</script>
</body>
</html>
macOS Touch ID 权限声明
如果你用 electron-builder 打包,需要在 extraResources 或构建配置中确保 Info.plist 包含 Face ID / Touch ID 使用说明:
# 在 electron-builder 的 afterSign 钩子中,或手动修改 Info.plist
# 添加以下键值:
/usr/libexec/PlistBuddy -c "Add :NSFaceIDUsageDescription string '用于 WebAuthn 生物识别认证'" your-app.app/Contents/Info.plist
不声明这个权限,macOS 会直接拒绝 Touch ID 弹窗,和之前 Electron 的 WebAuthn bug 表现类似——但根因不同,别混淆了。
运行示例
mkdir electron-webauthn-demo && cd electron-webauthn-demo
# 把上面四个文件放进去
npm install
npm start
点击"注册"按钮,macOS 上应弹出 Touch ID 提示(这正是 42.1.0 修复的场景),Windows 上则弹出 Windows Hello。验证流程同理。
升级建议与注意事项
- 直接升级 42.1.0 的风险很低:这是补丁版本,没有 API 变动或破坏性改动,只修 bug。如果你的项目卡在 42.0.x 且用户反馈了 Touch ID 问题,升级是零犹豫的选择。
- 确认你的 Chromium 基线:Electron 42 对应 Chromium 136,大版本跳跃意味着底层行为变化可能比你想象的多。从更早的版本(比如 30.x)跨多版本升级时,建议逐个大版本验证,不要一步到位。
- WebAuthn 的安全上下文要求:打包后的应用如果加载远程页面,必须确保该页面走 HTTPS。加载本地
file://内容时,Electron 在某些版本下会放宽 Secure Context 限制,但这不是规范保证的行为,生产环境应自建本地 HTTPS 服务器或使用自定义协议(protocol.registerSchemesAsPrivileged)。 - Touch ID / Face ID 权限声明是 macOS 独立要求:和 Electron 版本无关,不声明就不弹窗。这是很多开发者踩的坑,容易误以为是 Electron 的 bug。
如果你不涉及生物识别认证,42.1.0 对你来说只是例行更新,升级与否影响不大。但保持补丁版本的跟进本身就是低成本的安全实践——Chromium 的安全补丁总是随 Electron 版本一起发布的。