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 🙏

© 2026 – Pkg Stats / Ryan Hefner

electron-webauthn-mac-test

v1.0.6

Published

A native WebAuthn addon for Electron on macOS

Readme

electron-webauthn-mac

Native WebAuthn/Passkey support for Electron on macOS using Apple's AuthenticationServices framework.

Why This Addon?

The Web Authentication API (navigator.credentials) is the standard way to implement passkey authentication in web applications. However, in Electron applications 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.

Cross-Platform Implementation 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({
      rpId,
      userId,
      name: userName,
      displayName: userName
    });
  } else {
    // Use standard Web Authentication API on other platforms
    return await navigator.credentials.create({ publicKey: { /* ... */ } });
  }
}

Features

  • Platform authenticators — Touch ID, iCloud Keychain, QR code pairing (cross-device)
  • Security keys — External FIDO2 devices via USB, NFC, or BLE
  • PRF extension — Derive symmetric keys from passkeys (platform authenticators only)
  • LargeBlob extension — Store/retrieve arbitrary data on authenticator (platform authenticators only)
  • TypeScript support — Full type definitions included
  • Credential algorithm — ES256 (ECDSA P-256 with SHA-256), the only algorithm supported by Apple's AuthenticationServices

Platform vs Security Key authenticators: In Apple's AuthenticationServices API, "platform" authenticators are built-in or tightly integrated with the device — Touch ID, Face ID, iCloud Keychain passkeys, and cross-device authentication via QR code (where another Apple device acts as the authenticator). "Security key" authenticators are external FIDO2 hardware tokens connected via USB, NFC, or Bluetooth. PRF and LargeBlob extensions are only available for platform authenticators.

Requirements

| Feature | Minimum Version | |---------|-----------------| | Basic passkey operations | macOS 13.0+ | | Attachment type, credential transports | macOS 13.5+ | | LargeBlob extension | macOS 14.0+ | | Security key transports, appID | macOS 14.5+ | | PRF extension | macOS 15.0+ |

Build requirements:

  • Xcode 15+ with Command Line Tools
  • Node.js 18+ with node-gyp
  • Apple Developer account (for code signing)

Installation

npm install electron-webauthn-mac

TypeScript Support

TypeScript definitions are included. Import types directly:

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

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 Doe',
      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);
  }
}

API Reference

Note: All credentials use ES256 algorithm (ECDSA P-256 with SHA-256) — the only algorithm supported by Apple's AuthenticationServices.

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

Differences from Browser WebAuthn

This addon differs from the standard browser-based Web Authentication API (navigator.credentials). These differences stem from Apple's AuthenticationServices framework, which does not expose certain WebAuthn parameters.

challenge

In browser-based WebAuthn, the server generates a cryptographic challenge and sends it to the client, which includes it in the credential request. The server then verifies the signed challenge in the response.

This addon intentionally generates challenges internally to simplify the API — consumers don't need to handle challenge generation themselves. Challenges are created using SecRandomCopyBytes with 32 bytes of cryptographically secure random data:

The generated challenge can be retrieved from the returned clientDataJSON field (base64-encoded JSON containing the challenge, origin, and type). This design is suitable for Electron applications where traditional server-side challenge verification may not be required.

rpName

In browser-based WebAuthn, rp.name specifies a human-readable name for the relying party (e.g., "My Company") that is displayed to users during authentication prompts.

Apple's ASAuthorizationPlatformPublicKeyCredentialProvider constructor only accepts relyingPartyIdentifier (the domain string). There is no API to specify a separate display name. The framework shows the rpId domain value to users instead.

timeout

In browser-based WebAuthn, the timeout parameter specifies how long (in milliseconds) the user has to complete the authentication gesture before the operation fails.

Apple's ASAuthorizationPublicKeyCredentialRegistrationRequest and ASAuthorizationPublicKeyCredentialAssertionRequest do not provide properties to configure operation timeout. The system manages timeouts internally based on platform policies, and this behavior cannot be overridden.

Domain Association (Required for rpId)

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. Create the Association File

Create a file at https://your-domain.com/.well-known/apple-app-site-association with the following content:

{
  "webcredentials": {
    "apps": [
      "TEAM_ID.BUNDLE_ID"
    ]
  }
}

Example:

{
  "webcredentials": {
    "apps": [
      "A1B2C3D4E5.com.example.myapp"
    ]
  }
}

2. 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)

3. 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 Associated Domains Entitlement

In your Electron app's entitlements file, add:

<key>com.apple.developer.associated-domains</key>
<array>
  <string>webcredentials:your-domain.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.

Verification

After deployment, you can verify your association file:

  1. Visit https://your-domain.com/.well-known/apple-app-site-association in a browser
  2. Use Apple's Associated Domains Validator

Troubleshooting

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

Cause: The app is not running as a signed .app bundle.

Solution: Build the app with npm run build:mac and run from dist/mac-arm64/YourApp.app. Running with npm start or electron . will not work.


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

Cause: The rpId domain is not associated with your app. macOS requires a verified link between your app's bundle identifier and the domain used as rpId.

Solution: Set up domain association as described in Domain Association (Required for rpId):

  1. Host an apple-app-site-association file at https://your-domain.com/.well-known/apple-app-site-association
  2. Add the com.apple.developer.associated-domains entitlement to your app
  3. Ensure Team ID and Bundle ID match exactly

PRF or LargeBlob not working

Cause: These extensions are only supported for platform authenticators (Touch ID / iCloud Keychain), not security keys.

Solution: Use authenticators: ['platform'] to restrict to platform keys when using these extensions.

How It Works

The addon provides native WebAuthn/Passkey functionality using:

  • Swift: Core passkey logic using AuthenticationServices framework (shared/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

The example application demonstrates proper IPC setup, showing how to expose the addon from the main process to the renderer thread using Electron's contextBridge and ipcMain/ipcRenderer.

Example Electron App

The repository includes an example Electron application (example-electron-app/) demonstrating the addon usage.

⚠️ The example app must be built as a .app bundle and run from dist/. Running with npm start or electron . will fail due to macOS security requirements.

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

The example demonstrates creating passkeys, authenticating, and opening the system password manager.

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/
├── electron-webauthn-mac/     # Native addon package
│   ├── src/                   # C++/Objective-C bridge code
│   ├── js/                    # JavaScript wrapper + TypeScript definitions
│   ├── binding.gyp            # node-gyp build configuration
│   └── native/                # Prebuilt .node binary (generated)
├── shared/                    # Shared Swift code
│   └── PasskeyManager.swift   # Core WebAuthn logic (used by addon and test app)
├── example-electron-app/      # Example Electron application
└── test-mac-app/              # Native macOS test app (Xcode project)

Building the Addon

cd electron-webauthn-mac
npm install
npm run build

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

Test macOS App (Development Playground)

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

open test-mac-app/TestMacWebauthn.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 projects use the same PasskeyManager.swift from shared/

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

💡 Tip: When developing new features, prototype them in the test app first, then integrate into the Electron addon once verified.

Publishing

cd electron-webauthn-mac
npm run build        # Builds and copies .node to native/
npm publish          # prepublishOnly runs build automatically

License

MIT License

Copyright (c) 2025 Vault12 Inc.

See LICENSE file for full details.