electron-webauthn 0.0.14 → 0.0.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/README.md +470 -109
  2. package/dist/create/authorization-controller.d.ts +9 -0
  3. package/dist/create/authorization-controller.js +69 -0
  4. package/dist/create/handler.d.ts +30 -1
  5. package/dist/create/handler.js +149 -1
  6. package/dist/get/handler.d.ts +34 -22
  7. package/dist/get/handler.js +126 -166
  8. package/dist/get/internal-handler.d.ts +24 -0
  9. package/dist/get/internal-handler.js +157 -0
  10. package/dist/get/updated-handler.d.ts +1 -0
  11. package/dist/get/updated-handler.js +1 -0
  12. package/dist/helpers/client-data.d.ts +14 -0
  13. package/dist/helpers/client-data.js +36 -0
  14. package/dist/helpers/index.d.ts +1 -1
  15. package/dist/helpers/index.js +13 -10
  16. package/dist/helpers/presentation.d.ts +1 -0
  17. package/dist/helpers/presentation.js +12 -0
  18. package/dist/helpers/public-key.d.ts +1 -0
  19. package/dist/helpers/public-key.js +49 -0
  20. package/dist/helpers/rpid.d.ts +13 -0
  21. package/dist/helpers/rpid.js +59 -0
  22. package/dist/helpers/validation.d.ts +3 -0
  23. package/dist/helpers/validation.js +9 -0
  24. package/dist/objc/authentication-services/as-authorization-c-public-key-credential-descriptor.d.ts +9 -0
  25. package/dist/objc/authentication-services/as-authorization-c-public-key-credential-descriptor.js +6 -0
  26. package/dist/objc/authentication-services/as-authorization-platform-public-key-credential-provider.d.ts +3 -1
  27. package/dist/objc/authentication-services/as-authorization-platform-public-key-credential-registration.d.ts +15 -0
  28. package/dist/objc/authentication-services/as-authorization-platform-public-key-credential-registration.js +2 -0
  29. package/dist/objc/authentication-services/as-authorization-public-key-credential-large-blob-registration-input.d.ts +7 -0
  30. package/dist/objc/authentication-services/as-authorization-public-key-credential-large-blob-registration-input.js +6 -0
  31. package/dist/objc/authentication-services/as-authorization-public-key-credential-large-blob-registration-output.d.ts +5 -0
  32. package/dist/objc/authentication-services/as-authorization-public-key-credential-large-blob-registration-output.js +2 -0
  33. package/dist/objc/authentication-services/as-authorization-public-key-credential-prf-registration-input.d.ts +9 -0
  34. package/dist/objc/authentication-services/as-authorization-public-key-credential-prf-registration-input.js +6 -0
  35. package/dist/objc/authentication-services/as-authorization-public-key-credential-prf-registration-output.d.ts +9 -0
  36. package/dist/objc/authentication-services/as-authorization-public-key-credential-prf-registration-output.js +2 -0
  37. package/dist/objc/authentication-services/as-authorization-public-key-credential-registration.d.ts +14 -0
  38. package/dist/objc/authentication-services/as-authorization-public-key-credential-registration.js +2 -0
  39. package/dist/objc/authentication-services/enums/as-authorization-public-key-credential-attestation-kind.d.ts +6 -0
  40. package/dist/objc/authentication-services/enums/as-authorization-public-key-credential-attestation-kind.js +7 -0
  41. package/dist/objc/authentication-services/enums/as-authorization-public-key-credential-large-blob-support-requirement.d.ts +4 -0
  42. package/dist/objc/authentication-services/enums/as-authorization-public-key-credential-large-blob-support-requirement.js +5 -0
  43. package/dist/objc/authentication-services/enums/as-authorization-public-key-credential-user-verification-preference.d.ts +5 -0
  44. package/dist/objc/authentication-services/enums/as-authorization-public-key-credential-user-verification-preference.js +6 -0
  45. package/dist/test.d.ts +1 -0
  46. package/dist/test.js +5 -0
  47. package/package.json +2 -1
package/README.md CHANGED
@@ -1,23 +1,27 @@
1
1
  # electron-webauthn
2
2
 
3
- Add native WebAuthn/FIDO2 support to Electron using macOS AuthenticationServices framework.
3
+ Add native WebAuthn/FIDO2 support to Electron on macOS using its AuthenticationServices framework.
4
4
 
5
5
  ## Overview
6
6
 
7
7
  `electron-webauthn` is a TypeScript library that bridges Electron with the native macOS AuthenticationServices framework, enabling WebAuthn/FIDO2 authentication directly through native platform authenticators (Touch ID, Face ID, hardware security keys, etc.).
8
8
 
9
- This package provides JavaScript bindings to Apple's AuthenticationServices framework, allowing you to perform WebAuthn assertions (authentication/signing with existing credentials) in your Electron applications.
9
+ This package provides JavaScript bindings to Apple's AuthenticationServices framework, allowing you to perform WebAuthn assertions (authentication/signing with existing credentials) in your Electron applications using W3C WebAuthn-compliant APIs.
10
10
 
11
11
  ## Features
12
12
 
13
- - 🔐 Native WebAuthn support for Electron on macOS
14
- - 🎯 Support for platform authenticators (Touch ID, Face ID)
15
- - 🔑 Support for cross-platform authenticators (external security keys)
16
- - 📦 TypeScript first with complete type definitions
17
- - 🎨 Seamless integration with Electron's native window system
18
- - 🔐 PRF (Pseudo-Random Function) support for credential assertions
19
- - 💾 Large Blob support for storing credential-specific data
20
- - ⚙️ User verification preference configuration (preferred, required, discouraged)
13
+ - Native WebAuthn support for Electron on macOS
14
+ - Support for platform authenticators (Touch ID, Face ID)
15
+ - Support for cross-platform authenticators (external security keys)
16
+ - TypeScript first with complete type definitions
17
+ - W3C WebAuthn-compliant API
18
+ - Seamless integration with Electron's native window system
19
+ - PRF (Pseudo-Random Function) extension support
20
+ - Large Blob extension support for reading/writing credential-specific data
21
+ - User verification preference configuration (preferred, required, discouraged)
22
+ - Proper origin validation with public suffix list support
23
+ - Cross-origin iframe support with `topFrameOrigin`
24
+ - Per-credential PRF evaluation with `evalByCredential`
21
25
 
22
26
  ## Installation
23
27
 
@@ -40,81 +44,161 @@ yarn add electron-webauthn
40
44
  ```typescript
41
45
  import { getCredential } from "electron-webauthn";
42
46
  import { getPointer } from "objc-js";
47
+ import { BrowserWindow } from "electron";
43
48
 
44
49
  // In your Electron main process or preload script
45
- async function authenticate() {
50
+ async function authenticate(window: BrowserWindow, challenge: ArrayBuffer) {
46
51
  // Get the native window handle from your BrowserWindow
47
- const nativeWindowHandle = getPointer(
48
- yourElectronWindow.getNativeWindowHandle()
49
- );
52
+ const nativeWindowHandle = getPointer(window.getNativeWindowHandle());
50
53
 
51
- // Call getCredential to prompt the user for authentication
54
+ // Call getCredential with W3C WebAuthn-compliant options
52
55
  const result = await getCredential(
53
- "example.com", // Relying Party ID
54
- Buffer.from("your-challenge"), // Challenge from server
55
- "https://example.com", // Origin
56
- [], // Optional: allowed credential IDs
57
- "preferred" // Optional: user verification preference
56
+ {
57
+ challenge: challenge,
58
+ rpId: "example.com",
59
+ timeout: 60000, // Optional: 60 seconds
60
+ userVerification: "preferred", // Optional: "preferred" | "required" | "discouraged"
61
+ allowCredentials: [], // Optional: restrict to specific credentials
62
+ extensions: {
63
+ // Optional extensions
64
+ prf: {
65
+ eval: {
66
+ first: new Uint8Array(32), // 32 bytes
67
+ },
68
+ },
69
+ largeBlob: {
70
+ read: true,
71
+ },
72
+ },
73
+ },
74
+ {
75
+ currentOrigin: "https://example.com",
76
+ topFrameOrigin: "https://example.com", // Use for iframe support
77
+ nativeWindowHandle: nativeWindowHandle,
78
+ }
58
79
  );
59
80
 
60
- console.log(result);
61
- // Result contains:
62
- // - id: Credential ID (Buffer)
63
- // - authenticatorAttachment: 'platform' | 'cross-platform'
64
- // - clientDataJSON: Raw client data (Buffer)
65
- // - authenticatorData: Authenticator data (Buffer)
66
- // - signature: Assertion signature (Buffer)
67
- // - userHandle: User handle (Buffer)
68
- // - prf: [Buffer | null, Buffer | null] - PRF output (if supported)
69
- // - largeBlob: Buffer | null - Large blob data (if supported)
81
+ if (result.success) {
82
+ console.log("Authentication successful!");
83
+ console.log("Credential ID:", result.data.credentialId);
84
+ console.log("Signature:", result.data.signature);
85
+ // Result contains base64url-encoded strings ready to send to server
86
+ } else {
87
+ console.error("Authentication failed:", result.error);
88
+ }
70
89
  }
71
90
  ```
72
91
 
73
92
  ## API Reference
74
93
 
75
- ### `getCredential(rpid, challenge, origin, allowedCredentialIds, userVerificationPreference)`
94
+ ### `getCredential(publicKeyOptions, additionalOptions)`
76
95
 
77
- Performs a WebAuthn assertion (authentication) using available platform and cross-platform authenticators.
96
+ Performs a WebAuthn assertion (authentication) using available platform and cross-platform authenticators. This function follows the W3C WebAuthn specification.
78
97
 
79
98
  #### Parameters
80
99
 
81
- - **`rpid: string`** - The Relying Party ID (typically your domain)
82
- - **`challenge: Buffer`** - The challenge from your server (32+ bytes recommended)
83
- - **`origin: string`** - The origin where the credential assertion will be used (e.g., "https://example.com")
84
- - **`allowedCredentialIds: Buffer[]`** - Optional array of credential IDs the user can use (empty = all registered credentials)
85
- - **`userVerificationPreference?: UserVerificationPreference`** - Optional preference for user verification. Can be:
100
+ ##### `publicKeyOptions: PublicKeyCredentialRequestOptions`
101
+
102
+ You can plug the `publicKeyOptions` from `navigator.credentials.get()` directly onto this function.
103
+
104
+ Standard W3C WebAuthn credential request options:
105
+
106
+ - **`challenge: BufferSource`** (required) - The challenge from your server (32+ bytes recommended)
107
+ - **`rpId: string`** (required) - The Relying Party ID (typically your domain, e.g., "example.com")
108
+ - **`timeout?: number`** - Timeout in milliseconds (default: 10 minutes, max: 1 hour)
109
+ - **`userVerification?: string`** - User verification preference:
86
110
  - `"preferred"` - User verification is preferred but not required (default)
87
- - `"required"` - User verification is required
111
+ - `"required"` - User verification is required (e.g., biometric or PIN)
88
112
  - `"discouraged"` - User verification should be discouraged
113
+ - **`allowCredentials?: PublicKeyCredentialDescriptor[]`** - Optional array to restrict allowed credentials:
114
+ ```typescript
115
+ {
116
+ type: "public-key",
117
+ id: BufferSource, // Credential ID from registration
118
+ }
119
+ ```
120
+ - **`extensions?: AuthenticationExtensionsClientInputs`** - Optional WebAuthn extensions:
121
+ - **`prf`** - Pseudo-Random Function extension:
122
+ ```typescript
123
+ {
124
+ eval?: {
125
+ first: BufferSource, // 32+ bytes
126
+ second?: BufferSource // 32+ bytes (optional)
127
+ },
128
+ evalByCredential?: {
129
+ [base64UrlCredentialId: string]: {
130
+ first: BufferSource,
131
+ second?: BufferSource
132
+ }
133
+ }
134
+ }
135
+ ```
136
+ - **`largeBlob`** - Large Blob extension:
137
+ ```typescript
138
+ {
139
+ read?: boolean, // Read existing blob
140
+ write?: BufferSource // Write new blob data
141
+ }
142
+ ```
143
+
144
+ ##### `additionalOptions: WebauthnGetRequestOptions`
145
+
146
+ Additional options specific to the Electron environment:
147
+
148
+ - **`currentOrigin: string`** (required) - The origin of the requesting document (e.g., "https://example.com")
149
+ - **`topFrameOrigin: string | undefined`** - The origin of the top frame (for iframe support). Set to `currentOrigin` if not in an iframe
150
+ - **`nativeWindowHandle: Buffer`** (required) - Native window handle from `BrowserWindow.getNativeWindowHandle()` wrapped with `getPointer()` from `objc-js`
151
+ - **`isPublicSuffix?: (domain: string) => boolean`** - Optional function to check if a domain is a public suffix (e.g., "com", "co.uk"). Strongly recommended for security. Use a library like `tldts` for implementation
89
152
 
90
153
  #### Returns
91
154
 
92
- `Promise<GetCredentialResult>` - Resolves with the assertion result
155
+ `Promise<GetCredentialResult>` - Resolves with the assertion result or error
93
156
 
94
- #### Result Type
157
+ #### Result Types
95
158
 
96
159
  ```typescript
97
- interface GetCredentialResult {
98
- id: Buffer; // Credential ID
99
- authenticatorAttachment: "platform" | "cross-platform"; // Type of authenticator used
100
- clientDataJSON: Buffer; // Raw client data JSON (encoded as UTF-8 bytes)
101
- authenticatorData: Buffer; // Authenticator data from the device
102
- signature: Buffer; // Digital signature from the authenticator
103
- userHandle: Buffer; // User handle from the credential
104
- prf: [Buffer | null, Buffer | null]; // PRF output (if supported by authenticator)
105
- largeBlob: Buffer | null; // Large blob data (if supported by authenticator)
160
+ type GetCredentialResult =
161
+ | GetCredentialSuccessResult
162
+ | GetCredentialErrorResult;
163
+
164
+ interface GetCredentialSuccessResult {
165
+ success: true;
166
+ data: {
167
+ credentialId: string; // Base64url-encoded credential ID
168
+ clientDataJSON: string; // Base64url-encoded client data
169
+ authenticatorData: string; // Base64url-encoded authenticator data
170
+ signature: string; // Base64url-encoded signature
171
+ userHandle: string; // Base64url-encoded user handle
172
+ extensions?: {
173
+ prf?: {
174
+ results?: {
175
+ first: string; // Base64url-encoded PRF output
176
+ second?: string; // Base64url-encoded PRF output (if provided)
177
+ };
178
+ };
179
+ largeBlob?: {
180
+ blob?: string; // Base64url-encoded blob data (if read)
181
+ written?: boolean; // True if write succeeded (if write was requested)
182
+ };
183
+ };
184
+ };
106
185
  }
107
- ```
108
186
 
109
- #### User Verification Preference Type
110
-
111
- ```typescript
112
- type UserVerificationPreference = "preferred" | "required" | "discouraged";
187
+ interface GetCredentialErrorResult {
188
+ success: false;
189
+ error: "TypeError" | "AbortError" | "NotAllowedError" | "SecurityError";
190
+ }
113
191
  ```
114
192
 
115
193
  ## Architecture
116
194
 
117
- This library implements the WebAuthn standard using native APIs. Under the hood it handles all the complexity of macOS's authentication system, so you just call `getCredential()` with your challenge and get back the signed assertion.
195
+ This library implements the W3C WebAuthn standard using Apple's native AuthenticationServices framework. It provides a standards-compliant API that works seamlessly with existing WebAuthn servers and libraries. Under the hood, it:
196
+
197
+ - Validates origins and Relying Party IDs according to the WebAuthn specification
198
+ - Generates proper client data with `crossOrigin` field support (fixing Apple's default behavior)
199
+ - Handles both platform (Touch ID/Face ID) and cross-platform (security keys) authenticators simultaneously
200
+ - Properly manages WebAuthn extensions (PRF, Large Blob)
201
+ - Returns base64url-encoded values ready to send to your server for verification
118
202
 
119
203
  ## Usage Examples
120
204
 
@@ -129,14 +213,35 @@ let mainWindow: BrowserWindow;
129
213
 
130
214
  app.on("ready", () => {
131
215
  mainWindow = new BrowserWindow({
132
- webPreferences: { preload: "./preload.js" },
216
+ width: 800,
217
+ height: 600,
218
+ webPreferences: {
219
+ nodeIntegration: false,
220
+ contextIsolation: true,
221
+ preload: "./preload.js",
222
+ },
133
223
  });
224
+ mainWindow.loadURL("https://myapp.com");
134
225
  });
135
226
 
136
227
  // In your preload script or main process
137
- export async function authenticateUser(challenge: Buffer) {
228
+ export async function authenticateUser(challenge: ArrayBuffer) {
138
229
  const nativeHandle = getPointer(mainWindow.getNativeWindowHandle());
139
- return getCredential("myapp.com", challenge, "https://myapp.com", []);
230
+
231
+ const result = await getCredential(
232
+ {
233
+ challenge: challenge,
234
+ rpId: "myapp.com",
235
+ userVerification: "preferred",
236
+ },
237
+ {
238
+ currentOrigin: "https://myapp.com",
239
+ topFrameOrigin: "https://myapp.com",
240
+ nativeWindowHandle: nativeHandle,
241
+ }
242
+ );
243
+
244
+ return result;
140
245
  }
141
246
  ```
142
247
 
@@ -145,99 +250,355 @@ export async function authenticateUser(challenge: Buffer) {
145
250
  ```typescript
146
251
  // Only allow specific registered credentials
147
252
  const result = await getCredential(
148
- "myapp.com",
149
- challenge,
150
- "https://myapp.com",
151
- [credentialId1, credentialId2] // User can only use these credentials
253
+ {
254
+ challenge: challenge,
255
+ rpId: "myapp.com",
256
+ allowCredentials: [
257
+ { type: "public-key", id: credentialId1 },
258
+ { type: "public-key", id: credentialId2 },
259
+ ],
260
+ },
261
+ {
262
+ currentOrigin: "https://myapp.com",
263
+ topFrameOrigin: "https://myapp.com",
264
+ nativeWindowHandle: nativeHandle,
265
+ }
152
266
  );
153
267
  ```
154
268
 
155
- ### With User Verification Requirement
269
+ ### With Required User Verification
156
270
 
157
271
  ```typescript
158
272
  // Require user verification (e.g., for sensitive operations)
159
273
  const result = await getCredential(
160
- "myapp.com",
161
- challenge,
162
- "https://myapp.com",
163
- [], // No credential restrictions
164
- "required" // Require user verification (biometric or PIN)
274
+ {
275
+ challenge: challenge,
276
+ rpId: "myapp.com",
277
+ userVerification: "required", // Force biometric or PIN
278
+ },
279
+ {
280
+ currentOrigin: "https://myapp.com",
281
+ topFrameOrigin: "https://myapp.com",
282
+ nativeWindowHandle: nativeHandle,
283
+ }
284
+ );
285
+ ```
286
+
287
+ ### Using PRF Extension
288
+
289
+ The PRF (Pseudo-Random Function) extension allows you to derive cryptographic secrets from credentials:
290
+
291
+ ```typescript
292
+ // Generate a random salt for PRF
293
+ const prfSalt = crypto.getRandomValues(new Uint8Array(32));
294
+
295
+ const result = await getCredential(
296
+ {
297
+ challenge: challenge,
298
+ rpId: "myapp.com",
299
+ extensions: {
300
+ prf: {
301
+ eval: {
302
+ first: prfSalt,
303
+ // second: optionalSecondSalt, // Optional second output
304
+ },
305
+ },
306
+ },
307
+ },
308
+ {
309
+ currentOrigin: "https://myapp.com",
310
+ topFrameOrigin: "https://myapp.com",
311
+ nativeWindowHandle: nativeHandle,
312
+ }
165
313
  );
314
+
315
+ if (result.success && result.data.extensions?.prf?.results) {
316
+ const prfOutput = result.data.extensions.prf.results.first;
317
+ // Use prfOutput as a cryptographic key for encryption, etc.
318
+ console.log("PRF output:", prfOutput);
319
+ }
166
320
  ```
167
321
 
168
- ### Using PRF Output
322
+ ### Using PRF with Per-Credential Evaluation
169
323
 
170
324
  ```typescript
171
- // Get credential with PRF support
325
+ import { bufferToBase64Url } from "./helpers"; // You'll need to implement this
326
+
172
327
  const result = await getCredential(
173
- "myapp.com",
174
- challenge,
175
- "https://myapp.com",
176
- []
328
+ {
329
+ challenge: challenge,
330
+ rpId: "myapp.com",
331
+ allowCredentials: [
332
+ { type: "public-key", id: credentialId1 },
333
+ { type: "public-key", id: credentialId2 },
334
+ ],
335
+ extensions: {
336
+ prf: {
337
+ evalByCredential: {
338
+ [bufferToBase64Url(credentialId1)]: {
339
+ first: new Uint8Array(32), // Different salt for credential 1
340
+ },
341
+ [bufferToBase64Url(credentialId2)]: {
342
+ first: new Uint8Array(32), // Different salt for credential 2
343
+ },
344
+ },
345
+ },
346
+ },
347
+ },
348
+ {
349
+ currentOrigin: "https://myapp.com",
350
+ topFrameOrigin: "https://myapp.com",
351
+ nativeWindowHandle: nativeHandle,
352
+ }
353
+ );
354
+ ```
355
+
356
+ ### Reading and Writing Large Blobs
357
+
358
+ ```typescript
359
+ // Reading a large blob
360
+ const readResult = await getCredential(
361
+ {
362
+ challenge: challenge,
363
+ rpId: "myapp.com",
364
+ extensions: {
365
+ largeBlob: {
366
+ read: true,
367
+ },
368
+ },
369
+ },
370
+ {
371
+ currentOrigin: "https://myapp.com",
372
+ topFrameOrigin: "https://myapp.com",
373
+ nativeWindowHandle: nativeHandle,
374
+ }
177
375
  );
178
376
 
179
- // PRF output is available in the result
180
- const [prfFirst, prfSecond] = result.prf;
181
- if (prfFirst) {
182
- // Use PRF output for additional cryptographic operations
183
- console.log("PRF first output:", prfFirst);
377
+ if (readResult.success && readResult.data.extensions?.largeBlob?.blob) {
378
+ console.log("Large blob data:", readResult.data.extensions.largeBlob.blob);
379
+ }
380
+
381
+ // Writing a large blob
382
+ const dataToWrite = new TextEncoder().encode("Secret data");
383
+ const writeResult = await getCredential(
384
+ {
385
+ challenge: challenge,
386
+ rpId: "myapp.com",
387
+ extensions: {
388
+ largeBlob: {
389
+ write: dataToWrite,
390
+ },
391
+ },
392
+ },
393
+ {
394
+ currentOrigin: "https://myapp.com",
395
+ topFrameOrigin: "https://myapp.com",
396
+ nativeWindowHandle: nativeHandle,
397
+ }
398
+ );
399
+
400
+ if (writeResult.success && writeResult.data.extensions?.largeBlob?.written) {
401
+ console.log("Large blob written successfully");
184
402
  }
185
403
  ```
186
404
 
405
+ ### With Public Suffix List Validation
406
+
407
+ For enhanced security, use a public suffix list library like `tldts`:
408
+
409
+ ```typescript
410
+ import { getPublicSuffix } from "tldts";
411
+
412
+ const result = await getCredential(
413
+ {
414
+ challenge: challenge,
415
+ rpId: "myapp.com",
416
+ },
417
+ {
418
+ currentOrigin: "https://myapp.com",
419
+ topFrameOrigin: "https://myapp.com",
420
+ nativeWindowHandle: nativeHandle,
421
+ isPublicSuffix: (domain) => {
422
+ const suffix = getPublicSuffix(domain);
423
+ return suffix === domain;
424
+ },
425
+ }
426
+ );
427
+ ```
428
+
429
+ ### Supporting Cross-Origin Iframes
430
+
431
+ If your app loads WebAuthn requests from an iframe:
432
+
433
+ ```typescript
434
+ const result = await getCredential(
435
+ {
436
+ challenge: challenge,
437
+ rpId: "myapp.com",
438
+ },
439
+ {
440
+ currentOrigin: "https://auth.myapp.com", // The iframe's origin
441
+ topFrameOrigin: "https://myapp.com", // The parent page's origin
442
+ nativeWindowHandle: nativeHandle,
443
+ }
444
+ );
445
+ ```
446
+
187
447
  ### Server-Side Verification
188
448
 
189
- After getting the assertion result, verify it on your server:
449
+ After getting the assertion result, verify it on your server using any WebAuthn library:
190
450
 
191
451
  ```typescript
192
- // Server-side (Node.js, Python, etc.)
193
- // 1. Verify the signature using the public key from the credential registration
194
- // 2. Check that the challenge matches what you sent
195
- // 3. Verify the authenticator data flags
196
- // 4. Check the user handle matches the authenticated user
452
+ // Example with @simplewebauthn/server (Node.js)
453
+ import { verifyAuthenticationResponse } from "@simplewebauthn/server";
454
+
455
+ // The result.data object contains base64url-encoded values
456
+ const verification = await verifyAuthenticationResponse({
457
+ response: {
458
+ id: result.data.credentialId,
459
+ rawId: result.data.credentialId,
460
+ response: {
461
+ clientDataJSON: result.data.clientDataJSON,
462
+ authenticatorData: result.data.authenticatorData,
463
+ signature: result.data.signature,
464
+ userHandle: result.data.userHandle,
465
+ },
466
+ type: "public-key",
467
+ },
468
+ expectedChallenge: "expected-challenge-from-session",
469
+ expectedOrigin: "https://myapp.com",
470
+ expectedRPID: "myapp.com",
471
+ authenticator: {
472
+ credentialID: savedCredentialId,
473
+ credentialPublicKey: savedPublicKey,
474
+ counter: savedCounter,
475
+ },
476
+ });
477
+
478
+ if (verification.verified) {
479
+ console.log("Authentication successful!");
480
+ }
197
481
  ```
198
482
 
199
483
  ## Error Handling
200
484
 
201
- The `getCredential` promise will reject if:
202
-
203
- - The user cancels the authentication prompt
204
- - No valid credentials are available
205
- - The authenticator fails
206
- - The native window is invalid
485
+ The `getCredential` function returns a result object with a `success` field. Always check this field:
207
486
 
208
487
  ```typescript
209
- try {
210
- const result = await getCredential(
211
- "example.com",
212
- challenge,
213
- "https://example.com",
214
- []
215
- );
216
- } catch (error) {
217
- console.error("Authentication failed:", error.message);
218
- // Handle error appropriately
488
+ const result = await getCredential(publicKeyOptions, additionalOptions);
489
+
490
+ if (!result.success) {
491
+ // Handle specific error types
492
+ switch (result.error) {
493
+ case "TypeError":
494
+ console.error("Invalid parameters provided");
495
+ break;
496
+ case "NotAllowedError":
497
+ console.error("User cancelled or no credentials available");
498
+ break;
499
+ case "SecurityError":
500
+ console.error("Origin or rpId validation failed");
501
+ break;
502
+ case "AbortError":
503
+ console.error("Operation was aborted");
504
+ break;
505
+ }
506
+ return;
219
507
  }
508
+
509
+ // Success - process the credential
510
+ console.log("Credential ID:", result.data.credentialId);
220
511
  ```
221
512
 
222
- **Supported:**
513
+ ### Common Error Scenarios
514
+
515
+ - **NotAllowedError**: User cancelled the prompt, no valid credentials available, or the authenticator failed
516
+ - **SecurityError**: Origin doesn't match rpId, invalid origin format, or rpId is a public suffix
517
+ - **TypeError**: Invalid parameter types (missing required fields, wrong data types)
518
+ - **AbortError**: Operation timeout or explicitly aborted
519
+
520
+ ## Feature Support
521
+
522
+ **Currently Supported:**
223
523
 
224
524
  - ✅ WebAuthn assertions (authentication with existing credentials)
225
- - ✅ Cross-platform authenticators (external security keys)
525
+ - ✅ Cross-platform authenticators (external security keys like YubiKey)
226
526
  - ✅ Platform authenticators (Touch ID, Face ID)
227
- - ✅ PRF (Pseudo-Random Function) output
228
- - ✅ Large Blob support
527
+ - ✅ PRF (Pseudo-Random Function) extension
528
+ - ✅ Global evaluation (`eval`)
529
+ - ✅ Per-credential evaluation (`evalByCredential`)
530
+ - ✅ Large Blob extension
531
+ - ✅ Reading blobs
532
+ - ✅ Writing blobs
533
+ - ✅ User verification preferences
534
+ - ✅ Credential filtering with `allowCredentials`
535
+ - ✅ Proper origin and rpId validation
536
+ - ✅ Cross-origin iframe support
537
+ - ✅ W3C WebAuthn-compliant client data generation
538
+
539
+ **Not Yet Supported:**
540
+
541
+ - ❌ Credential registration (attestation) - coming soon
542
+ - ❌ Discoverable credentials (resident keys)
543
+ - ❌ Conditional UI
544
+ - ❌ Other WebAuthn extensions (credProtect, minPinLength, etc.)
545
+
546
+ ## Best Practices
547
+
548
+ ### Security Recommendations
549
+
550
+ 1. **Always use HTTPS origins** in production (unless testing on `localhost`)
551
+ 2. **Implement public suffix list validation** using `tldts` or similar library
552
+ 3. **Validate rpId carefully** - it should match your domain
553
+ 4. **Generate strong challenges** - use at least 32 random bytes from a CSPRNG
554
+ 5. **Verify assertions on your server** - never trust client-side validation alone
555
+ 6. **Store credential public keys securely** - needed for signature verification
556
+ 7. **Implement proper timeout handling** - don't leave prompts open indefinitely
557
+
558
+ ### Performance Tips
559
+
560
+ 1. **Cache native window handles** - no need to get them on every call
561
+ 2. **Reuse challenge buffers** when possible
562
+ 3. **Set appropriate timeouts** - shorter for better UX, longer for hardware keys
563
+ 4. **Handle user cancellation gracefully** - don't retry automatically
564
+
565
+ ### TypeScript Types
229
566
 
230
- **Not Supported:**
567
+ This library exports all necessary TypeScript types. Import them for type safety:
231
568
 
232
- - ❌ Credential registration (attestation)
233
- - Discoverable credentials
569
+ ```typescript
570
+ import type {
571
+ GetCredentialResult,
572
+ GetCredentialSuccessData,
573
+ PublicKeyCredentialRequestOptions,
574
+ AuthenticationExtensionsClientInputs,
575
+ PRFInput,
576
+ } from "electron-webauthn";
577
+ ```
578
+
579
+ ## Debugging
580
+
581
+ Enable detailed logging by checking the console output. The library logs warnings for:
582
+
583
+ - PRF extension enabled but no input values provided
584
+ - Large blob write enabled but no data provided
585
+ - Authorization errors with native error messages
586
+
587
+ ## Known Limitations
588
+
589
+ - **PRF and Large Blob**: Only supported on platform authenticators (Touch ID/Face ID), not security keys on macOS
234
590
 
235
591
  ## License
236
592
 
237
593
  See [LICENSE](./LICENSE) file for details.
238
594
 
595
+ ## Contributing
596
+
597
+ Contributions are welcome! Please feel free to submit issues and pull requests.
598
+
239
599
  ## Resources
240
600
 
241
- - [WebAuthn Specification](https://www.w3.org/TR/webauthn-2/)
601
+ - [W3C WebAuthn Level 3 Specification](https://www.w3.org/TR/webauthn-3/)
602
+ - [WebAuthn Guide](https://webauthn.guide/)
242
603
  - [Electron Documentation](https://www.electronjs.org/docs)
243
604
  - [objc-js Library](https://github.com/iamEvanYT/objc-js)