npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

electron-webauthn-mac

v1.0.0

Published

A native WebAuthn addon for Electron on macOS

Readme

electron-webauthn-mac


Contents

Why This Addon?

The Web Authentication API navigator.credentials is the standard way to implement passkey authentication in web applications. However, in Electron applications running on macOS, this API is currently broken and non-functional due to platform-specific limitations (see electron/electron#24573).

This addon serves as a native implementation and polyfill for macOS, providing direct access to Apple's AuthenticationServices framework. It allows Electron applications to use passkey authentication on macOS while maintaining the option to use the standard Web Authentication API on other platforms.

Features

  • Platform & security key authenticators: Support for Touch ID, iCloud Keychain, cross-device QR pairing, and external FIDO2 keys
  • PRF extension: Derive symmetric keys from passkeys for client-side encryption (platform authenticators only)
  • LargeBlob extension: Store and retrieve arbitrary data on the authenticator (platform authenticators only)
  • System integration: Open macOS password manager directly from your Electron app
  • TypeScript support: Full type definitions included

Quick Start

1. Install the addon

npm install electron-webauthn-mac

2. Use the API

Basic example:

const webauthn = require('electron-webauthn-mac');

// Create a new passkey
async function registerUser() {
  try {
    const credential = await webauthn.createCredential({
      rpId: 'example.com', userId: 'user123', name: 'John', displayName: 'John Doe'
    });
    console.log('Created credential:', credential);
  } catch (error) {
    console.error('Registration failed:', error);
  }
}

// Authenticate with an existing passkey
async function authenticateUser() {
  try {
    const assertion = await webauthn.getCredential({ rpId: 'example.com' });
    console.log('Authentication successful:', assertion);
  } catch (error) {
    console.error('Authentication failed:', error);
  }
}

[!TIP] For cross-platform implementation, use the following pattern:

async function createPasskey(userId, userName, rpId) {
  if (process.platform === 'darwin') { // Use native addon on macOS
    const webauthn = require('electron-webauthn-mac');
    return await webauthn.createCredential({ ... });
  } else { // Use standard Web Authentication API on other platforms
    return await navigator.credentials.create({ ... });
  }
}

[!NOTE] TypeScript definitions are included. Import types directly:

import type {
  CreateCredentialOptions, GetCredentialOptions,
  RegistrationCredential, AssertionCredential
} from 'electron-webauthn-mac';

3. Configure the entitlements and domain association

Unlike browser-based WebAuthn, macOS requires your app to prove it has association with the domain used as rpId. Follow the steps in Configuring Entitlements and Domain Association to set this up. See Why is domain association required? for details.

4. Sign and run the app

Your Electron app must be code-signed to embed the entitlements from step 3 into the final .app bundle. Running with npm start or electron . will launch the app, but passkey operations will fail because the unsigned process lacks an application identifier.

Use a tool like electron-builder to build a signed .app bundle. See the Example Electron App for a working configuration.

Example Electron App

The repository includes an example Electron application demonstrating the addon usage. It shows how to expose the addon from the main process to the renderer thread using Electron's contextBridge and ipcMain/ipcRenderer.

[!IMPORTANT] Before running the example app, complete Configure the entitlements and domain association and Sign and run the app from Quick Start.

cd example-electron-app
npm install
npm run build:mac
open dist/mac-arm64/WebAuthnDemo.app

Configuring Entitlements and Domain Association

For WebAuthn to work with your domain (rpId), you must establish an association between your app and the domain. This is done by hosting an apple-app-site-association file on your server. See Apple's Associated Domains documentation for details.

1. Find Your Team ID and Bundle ID

  • Team ID: Found in Apple Developer Portal → Membership Details
  • Bundle ID: Your app's bundle identifier (e.g., com.yourcompany.yourapp)

2. Create the Association File

Host an associated domain file on your website (with a URL such as https://example.com/.well-known/apple-app-site-association) with the following content:

{
  "webcredentials": {
    "apps": [ "TEAM_ID.BUNDLE_ID" ] // Example: "A1B2C3D4E5.com.example.myapp"
  }
}

3. Confirm Server Requirements

The file must be:

  • Served over HTTPS (valid SSL certificate required)
  • Content-Type: application/json
  • Accessible without redirects at the exact path /.well-known/apple-app-site-association
  • No .json extension in the URL

4. Add Entitlements

In your Electron app's entitlements file, add:

<!-- Replace with your Team ID and Bundle ID -->
<key>com.apple.application-identifier</key>
<string>TEAM_ID.BUNDLE_ID</string>
<!-- Replace example.com with your domain name -->
<key>com.apple.developer.associated-domains</key>
<array>
  <string>webcredentials:example.com</string>
</array>

[!NOTE] The domain in rpId must exactly match the domain in your associated domains entitlement and the domain hosting the apple-app-site-association file.

5. Verification

After deployment, you can verify your association file:

  1. Visit https://example.com/.well-known/apple-app-site-association in a browser
  2. Use an external validator like Branch.io AASA Validator or similar tools

API Reference

createCredential(options)

Creates a new passkey credential using Touch ID, iCloud Keychain, or an external security key.

Options: | Property | Type | Required | Description | |----------|------|----------|-------------| | rpId | string | ✅ | Relying Party identifier (your domain, e.g., "example.com") | | userId | string | ✅ | Stable user identifier (max 64 bytes recommended) | | name | string | ✅ | User's name (used for both platform and security key authentication) | | displayName | string | ✅ | User's display name (used for security key only) | | authenticators | string[] | | Which authenticator types to offer: ['platform', 'securityKey'] (default: both) | | excludeCredentials | object[] | | Existing credentials to prevent re-registration | | userVerification | string | | 'required', 'preferred' (default), or 'discouraged' | | attestation | string | | 'none' (default), 'indirect', 'direct', or 'enterprise' | | largeBlobRequired | boolean | | Require largeBlob support (macOS 14.0+, platform keys only) | | prf | object | | PRF extension request (macOS 15.0+, platform keys only) |

Returns: Promise<RegistrationCredential>

Platform credential response (Touch ID / iCloud Keychain):

{
  type: "platform",
  credentialID: string,           // Base64-encoded credential ID
  attestationObject: string,      // Base64-encoded CBOR attestation
  clientDataJSON: string,         // Base64-encoded client data
  attachment?: string,            // "platform" or "crossPlatform" (macOS 13.5+)
  largeBlobSupported?: boolean,   // Whether largeBlob is supported (macOS 14.0+)
  prfEnabled?: boolean,           // Whether PRF extension is supported (macOS 15.0+)
  prfFirst?: string,              // Base64-encoded first PRF output (if requested)
  prfSecond?: string              // Base64-encoded second PRF output (if requested)
}

Security key credential response (external FIDO2 key):

{
  type: "securityKey",
  credentialID: string,           // Base64-encoded credential ID
  attestationObject: string,      // Base64-encoded CBOR attestation
  clientDataJSON: string,         // Base64-encoded client data
  transports?: string[]           // ["usb", "nfc", "ble", "internal", "hybrid"] (macOS 14.5+)
}

getCredential(options)

Authenticates a user using an existing passkey.

Options: | Property | Type | Required | Description | |----------|------|----------|-------------| | rpId | string | ✅ | Relying Party identifier (your domain) | | authenticators | string[] | | Which authenticator types to offer: ['platform', 'securityKey'] (default: both) | | allowCredentials | object[] | | Specific credentials to allow (if not set, discovers available) | | userVerification | string | | 'required', 'preferred' (default), or 'discouraged' | | largeBlobOperation | object | | { read: true } or { write: "base64data" } (macOS 14.0+, platform keys only) | | prf | object | | { eval: { first: "base64", second?: "base64" } } (macOS 15.0+, platform keys only) |

Returns: Promise<AssertionCredential>

Platform credential response (Touch ID / iCloud Keychain):

{
  type: "platform",
  userID: string,                 // Base64-encoded user handle
  credentialID: string,           // Base64-encoded credential ID
  authenticatorData: string,      // Base64-encoded authenticator data
  clientDataJSON: string,         // Base64-encoded client data
  signature: string,              // Base64-encoded signature
  attachment?: string,            // "platform" or "crossPlatform" (macOS 13.5+)
  largeBlobResult?: object,       // { type: 'read', data } or { type: 'write', success } (macOS 14.0+)
  prfEnabled?: boolean,           // Whether PRF extension was used (macOS 15.0+)
  prfFirst?: string,              // Base64-encoded first PRF output
  prfSecond?: string              // Base64-encoded second PRF output
}

Security key credential response (external FIDO2 key):

{
  type: "securityKey",
  userID: string,                 // Base64-encoded user handle
  credentialID: string,           // Base64-encoded credential ID
  authenticatorData: string,      // Base64-encoded authenticator data
  clientDataJSON: string,         // Base64-encoded client data
  signature: string,              // Base64-encoded signature
  appID?: boolean                 // Whether legacy FIDO U2F appID was used (macOS 14.5+)
}

managePasswords()

Opens the macOS system password manager (Settings → Passwords).

Parameters: None

Returns: void

macOS Platform Quirks

How It Works

The addon provides native WebAuthn/Passkey functionality using:

  • Swift: Core passkey logic using AuthenticationServices framework (src/PasskeyManager.swift)
  • Objective-C: Bridge between Swift and C++
  • C++: N-API bindings for Node.js integration
  • JavaScript/TypeScript: User-friendly wrapper API with full type definitions

[!NOTE] All credentials use ES256 algorithm (ECDSA P-256 with SHA-256) — the only algorithm supported by Apple's AuthenticationServices.

Why is domain association required?

Passkeys are tied to a specific domain (like example.com). When you authenticate with a passkey, macOS needs to verify that the app requesting access actually owns that domain — otherwise, a malicious app could impersonate your bank and steal your credentials.

Apple enforces this through a two-way trust mechanism:

  1. Your server proves it trusts the app — by hosting a file at https://example.com/.well-known/apple-app-site-association that lists your app's bundle identifier
  2. Your app declares which domain it represents — via the com.apple.developer.associated-domains entitlement embedded during code signing

When both sides match, macOS allows your app to create and use passkeys for that domain. Without this setup, passkey operations will fail with "Application is not associated with domain" errors.

[!NOTE] In browsers, localhost is exempt from domain verification for development convenience. Native macOS code has no such exception — domain association is always required, even for local testing. You'll need a real domain with HTTPS to develop and test passkey functionality with this addon.

Platform vs Security Key authenticators

Apple's AuthenticationServices framework distinguishes between two authenticator types (see Apple's documentation):

| Type | What it is | Examples | |------|------------|----------| | Platform | Built-in or tightly integrated with the device | Touch ID, Face ID, iCloud Keychain, cross-device via QR code | | Security Key | External FIDO2 hardware tokens | YubiKey, Titan Key, etc (via USB, NFC, or Bluetooth) |

As of 2025, PRF and LargeBlob extensions are only available for platform authenticators.

Differences from Browser WebAuthn

This addon differs from the standard browser-based Web Authentication API (navigator.credentials) due to Apple's AuthenticationServices framework limitations:

| Parameter | Browser WebAuthn | This Addon | |-----------|------------------|------------| | challenge | Server-generated, passed to API | Auto-generated internally (32 bytes via SecRandomCopyBytes). Retrieve from clientDataJSON if needed. | | rp.name | Human-readable RP name shown to user | Not supported — macOS shows rpId domain instead | | timeout | Configurable operation timeout | Not supported — system manages timeouts internally | | pubKeyCredParams | Multiple algorithms supported | ES256 only (hardcoded by Apple) | | PRF, LargeBlob | Available on all authenticators | Platform authenticators only — see Platform vs Security Key |

Troubleshooting

"The calling process does not have an application identifier. Make sure it is properly configured."

The app is not running as a signed .app bundle. Follow Sign and run the app to build and code-sign your application.

"Application is not associated with domain" or "No credentials available"

The rpId domain is not associated with your app. Follow Configuring Entitlements and Domain Association and verify that your Team ID and Bundle ID match in both the server-hosted file and your app's entitlements.

PRF or LargeBlob extensions are not working

These extensions are only supported for platform authenticators (Touch ID / iCloud Keychain), not security keys. Use authenticators: ['platform'] to restrict to platform keys when using these extensions.

Plugin Development

Prerequisites

  • macOS 13.0+ (Ventura or later)
  • Xcode 15+ with Command Line Tools (xcode-select --install)
  • Node.js 18+ with npm
  • Apple Developer account (for code signing)

Project Structure

electron-webauthn-mac/
├── src/                       # Swift/Objective-C/C++ source code
│   └── PasskeyManager.swift   # Core WebAuthn logic (used by addon and dev app)
├── js/                        # JavaScript wrapper + TypeScript definitions
├── include/                   # Header file
├── native/                    # Prebuilt .node binary (generated)
├── binding.gyp                # node-gyp build configuration
├── example-electron-app/      # Example Electron application
└── dev-mac-app/               # Native macOS dev app (Xcode project)

Building the Addon

npm install
npm run build

This compiles the Swift code, builds the native addon, and copies the .node binary to native/.

WebAuthn Playground (Development App)

For faster iteration during development, use the native macOS dev app:

open dev-mac-app/WebAuthnPlayground.xcodeproj
# Build and run in Xcode (⌘R)

Why a separate native app?

Building through Electron requires code signing and bundling — which is slow. The native app provides:

  • Instant iteration — build and run directly from Xcode in seconds
  • Full debugger access — set breakpoints in Swift code, inspect variables
  • API playground — quickly test new AuthenticationServices features
  • Shared codebase — both Electron example and Playground projects use the same PasskeyManager.swift from src/

The playground includes buttons for all WebAuthn operations: registration, authentication, PRF encryption/decryption, and largeBlob read/write.

[!TIP] When developing new features, prototype them in the playground first, then integrate into the Electron addon once verified.

Publishing

npm run build        # Builds and copies .node to native/
npm publish          # prepublishOnly runs build automatically

License

This project is released under the MIT License.