ngx-webauthn 0.0.2 โ†’ 0.2.0

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.
package/README.md CHANGED
@@ -1,412 +1,922 @@
1
- # ngx-webauthn
2
-
3
- A comprehensive Angular library that provides a clean, type-safe abstraction over the WebAuthn API. This library simplifies the implementation of passwordless authentication using passkeys, security keys, and biometric authentication.
4
-
5
- ## Features
6
-
7
- - ๐Ÿ” **Complete WebAuthn Support** - Registration and authentication flows
8
- - ๐Ÿ›ก๏ธ **Type Safety** - Full TypeScript support with comprehensive interfaces
9
- - ๐ŸŽฏ **Clean API** - Simplified abstraction over complex WebAuthn APIs
10
- - ๐Ÿ“ฑ **Cross-Platform** - Works with platform authenticators (Face ID, Touch ID, Windows Hello)
11
- - ๐Ÿ”‘ **Security Keys** - Support for external authenticators (YubiKey, etc.)
12
- - โšก **RxJS Integration** - Observable-based API that fits naturally into Angular
13
- - ๐Ÿงช **Well Tested** - Comprehensive unit test coverage
14
- - ๐Ÿ“– **Browser Support** - Works in all modern browsers with WebAuthn support
15
-
16
- ## Installation
17
-
18
- ```bash
19
- npm install ngx-webauthn
20
- ```
21
-
22
- ## Quick Start
23
-
24
- ### 1. Configure the Service
25
-
26
- First, configure the WebAuthn service with your relying party information:
27
-
28
- ```typescript
29
- // main.ts (standalone app)
30
- import { bootstrapApplication } from '@angular/platform-browser';
31
- import { provideWebAuthn } from 'ngx-webauthn';
32
- import { AppComponent } from './app/app.component';
33
-
34
- bootstrapApplication(AppComponent, {
35
- providers: [
36
- provideWebAuthn(
37
- { name: 'My App', id: 'myapp.com' }, // Required relying party config
38
- { defaultTimeout: 30000 } // Optional overrides
39
- ),
40
- // ... other providers
41
- ],
42
- });
43
- ```
44
-
45
- ```typescript
46
- // app.module.ts (module-based app)
47
- import { NgModule } from '@angular/core';
48
- import { BrowserModule } from '@angular/platform-browser';
49
- import { WEBAUTHN_CONFIG, createWebAuthnConfig } from 'ngx-webauthn';
50
-
51
- @NgModule({
52
- imports: [BrowserModule],
53
- providers: [
54
- {
55
- provide: WEBAUTHN_CONFIG,
56
- useValue: createWebAuthnConfig({ name: 'My App', id: 'myapp.com' }, { defaultTimeout: 30000 }),
57
- },
58
- ],
59
- // ...
60
- })
61
- export class AppModule {}
62
- ```
63
-
64
- ### 2. Use the Service with Presets
65
-
66
- ```typescript
67
- import { Component, inject } from '@angular/core';
68
- import { WebAuthnService, WebAuthnError, WebAuthnErrorType } from 'ngx-webauthn';
69
-
70
- @Component({
71
- selector: 'app-auth',
72
- template: `
73
- <div>
74
- <button (click)="register()" [disabled]="!isSupported">Register</button>
75
- <button (click)="authenticate()" [disabled]="!isSupported">Authenticate</button>
76
- <p *ngIf="!isSupported">WebAuthn is not supported in this browser</p>
77
- </div>
78
- `,
79
- })
80
- export class AuthComponent {
81
- private webAuthnService = inject(WebAuthnService);
82
-
83
- isSupported = this.webAuthnService.isSupported();
84
-
85
- register() {
86
- this.webAuthnService
87
- .register({
88
- username: 'john.doe@example.com',
89
- preset: 'passkey', // Easy preset configuration!
90
- })
91
- .subscribe({
92
- next: (result) => {
93
- console.log('Registration successful:', result);
94
- // Send result.credentialId, result.publicKey, etc. to your server
95
- },
96
- error: (error: WebAuthnError) => {
97
- console.error('Registration failed:', error.message);
98
- if (error.type === WebAuthnErrorType.USER_CANCELLED) {
99
- // User cancelled or denied permission
100
- }
101
- },
102
- });
103
- }
104
-
105
- authenticate() {
106
- this.webAuthnService
107
- .authenticate({
108
- preset: 'passkey', // Uses the same preset for consistency
109
- // Optional: specify allowed credentials
110
- allowCredentials: ['credential-id-from-registration'],
111
- })
112
- .subscribe({
113
- next: (result) => {
114
- console.log('Authentication successful:', result);
115
- // Send result to your server for verification
116
- },
117
- error: (error: WebAuthnError) => {
118
- console.error('Authentication failed:', error.message);
119
- },
120
- });
121
- }
122
- }
123
- ```
124
-
125
- ### 2. Check Browser Support
126
-
127
- ```typescript
128
- import { Component, OnInit, inject } from '@angular/core';
129
- import { WebAuthnService } from 'ngx-webauthn';
130
-
131
- @Component({
132
- selector: 'app-support-check',
133
- template: `
134
- <div *ngIf="support">
135
- <p>WebAuthn Support: {{ support.isSupported ? 'Yes' : 'No' }}</p>
136
- <p>Platform Authenticator: {{ support.isPlatformAuthenticatorAvailable ? 'Available' : 'Not Available' }}</p>
137
- <p>Supported Transports: {{ support.supportedTransports.join(', ') }}</p>
138
- </div>
139
- `,
140
- })
141
- export class SupportCheckComponent implements OnInit {
142
- private webAuthnService = inject(WebAuthnService);
143
- support: any;
144
-
145
- ngOnInit() {
146
- this.webAuthnService.getSupport().subscribe({
147
- next: (support) => {
148
- this.support = support;
149
- },
150
- error: (error) => {
151
- console.error('Failed to check WebAuthn support:', error);
152
- },
153
- });
154
- }
155
- }
156
- ```
157
-
158
- ## Features Overview
159
-
160
- ### ๐ŸŽฏ **Preset System**
161
-
162
- The library includes intelligent presets for common WebAuthn use cases:
163
-
164
- - **`passkey`** - Modern passwordless authentication with credential syncing
165
- - **`secondFactor`** - Traditional 2FA with hardware security keys
166
- - **`deviceBound`** - High-security single-device authentication
167
-
168
- ```typescript
169
- // Simple preset usage
170
- this.webAuthnService.register({ username: 'user@example.com', preset: 'passkey' });
171
-
172
- // Preset with custom overrides
173
- this.webAuthnService.register({
174
- username: 'user@example.com',
175
- preset: 'passkey',
176
- authenticatorSelection: { userVerification: 'required' },
177
- });
178
- ```
179
-
180
- ### ๐Ÿ”ง **Enhanced Configuration**
181
-
182
- The new configuration system ensures proper security defaults:
183
-
184
- ```typescript
185
- interface WebAuthnConfig {
186
- relyingParty: {
187
- name: string; // Required: Your app name
188
- id?: string; // Optional: Your domain
189
- };
190
- defaultTimeout?: number; // Default: 60000ms
191
- defaultAlgorithms?: PublicKeyCredentialParameters[];
192
- enforceUserVerification?: boolean; // Default: false
193
- defaultAttestation?: AttestationConveyancePreference;
194
- defaultAuthenticatorSelection?: AuthenticatorSelectionCriteria;
195
- }
196
- ```
197
-
198
- ### ๐Ÿ”„ **Flexible Input Types**
199
-
200
- Supports multiple input formats for maximum flexibility:
201
-
202
- ```typescript
203
- // High-level config (recommended)
204
- service.register({ username: 'user@example.com', preset: 'passkey' });
205
-
206
- // Native WebAuthn options
207
- service.register(nativeCreationOptions);
208
-
209
- // JSON WebAuthn options (base64url strings)
210
- service.register(jsonCreationOptions);
211
- ```
212
-
213
- ## API Reference
214
-
215
- ### WebAuthnService
216
-
217
- #### Methods
218
-
219
- ##### `isSupported(): boolean`
220
-
221
- Synchronously checks if WebAuthn is supported in the current browser.
222
-
223
- ##### `getSupport(): Observable<WebAuthnSupport>`
224
-
225
- Returns detailed information about WebAuthn support including platform authenticator availability.
226
-
227
- ##### `register(input: RegisterInput): Observable<RegistrationResponse>`
228
-
229
- Registers a new WebAuthn credential. Accepts either high-level config objects or direct WebAuthn options.
230
-
231
- ##### `authenticate(input: AuthenticateInput): Observable<AuthenticationResponse>`
232
-
233
- Authenticates using an existing WebAuthn credential. Accepts either high-level config objects or direct WebAuthn options.
234
-
235
- ### Response Types
236
-
237
- #### RegistrationResponse
238
-
239
- ```typescript
240
- interface RegistrationResponse {
241
- success: boolean;
242
- credentialId: string; // Base64url encoded
243
- publicKey?: string; // Base64url encoded (if available)
244
- transports?: AuthenticatorTransport[];
245
- rawResponse?: WebAuthnRegistrationResult; // For advanced usage
246
- }
247
- ```
248
-
249
- #### AuthenticationResponse
250
-
251
- ```typescript
252
- interface AuthenticationResponse {
253
- success: boolean;
254
- credentialId: string; // Base64url encoded
255
- userHandle?: string; // Base64url encoded
256
- rawResponse?: WebAuthnAuthenticationResult; // For advanced usage
257
- }
258
- ```
259
-
260
- #### WebAuthnRegistrationResult
261
-
262
- ```typescript
263
- interface WebAuthnRegistrationResult {
264
- credentialId: string; // Base64url encoded
265
- publicKey: string; // Base64url encoded
266
- attestationObject: string; // Base64url encoded
267
- clientDataJSON: string; // Base64url encoded
268
- transports?: AuthenticatorTransport[];
269
- }
270
- ```
271
-
272
- #### WebAuthnAuthenticationResult
273
-
274
- ```typescript
275
- interface WebAuthnAuthenticationResult {
276
- credentialId: string; // Base64url encoded
277
- authenticatorData: string; // Base64url encoded
278
- clientDataJSON: string; // Base64url encoded
279
- signature: string; // Base64url encoded
280
- userHandle?: string; // Base64url encoded
281
- }
282
- ```
283
-
284
- ### Error Handling
285
-
286
- The library provides structured error handling through the `WebAuthnError` class:
287
-
288
- ```typescript
289
- enum WebAuthnErrorType {
290
- NOT_SUPPORTED = 'NOT_SUPPORTED',
291
- NOT_ALLOWED = 'NOT_ALLOWED',
292
- INVALID_STATE = 'INVALID_STATE',
293
- CONSTRAINT = 'CONSTRAINT',
294
- SECURITY = 'SECURITY',
295
- NETWORK = 'NETWORK',
296
- ABORT = 'ABORT',
297
- TIMEOUT = 'TIMEOUT',
298
- ENCODING = 'ENCODING',
299
- UNKNOWN = 'UNKNOWN',
300
- }
301
-
302
- class WebAuthnError extends Error {
303
- constructor(public readonly type: WebAuthnErrorType, message: string, public readonly originalError?: Error);
304
- }
305
- ```
306
-
307
- ## Advanced Usage
308
-
309
- ### Using Direct WebAuthn Options
310
-
311
- ```typescript
312
- // Native creation options
313
- const creationOptions: PublicKeyCredentialCreationOptions = {
314
- rp: { name: 'My App' },
315
- user: {
316
- id: new Uint8Array([1, 2, 3, 4]),
317
- name: 'john.doe@example.com',
318
- displayName: 'John Doe',
319
- },
320
- challenge: new Uint8Array([5, 6, 7, 8]),
321
- pubKeyCredParams: [{ type: 'public-key', alg: -7 }],
322
- };
323
-
324
- this.webAuthnService.register(creationOptions).subscribe((result) => {
325
- console.log('Registration complete:', result);
326
- });
327
- ```
328
-
329
- ### JSON WebAuthn Options
330
-
331
- ```typescript
332
- // JSON options also work (base64url strings instead of ArrayBuffers)
333
- const jsonOptions: PublicKeyCredentialCreationOptionsJSON = {
334
- rp: { name: 'My App' },
335
- user: {
336
- id: 'dXNlcklk',
337
- name: 'john.doe@example.com',
338
- displayName: 'John Doe',
339
- },
340
- challenge: 'Y2hhbGxlbmdl',
341
- pubKeyCredParams: [{ type: 'public-key', alg: -7 }],
342
- };
343
-
344
- this.webAuthnService.register(jsonOptions).subscribe((result) => {
345
- console.log('Registration complete:', result);
346
- });
347
- ```
348
-
349
- ### Config with Custom Challenge
350
-
351
- ```typescript
352
- // High-level config with custom challenge
353
- this.webAuthnService
354
- .register({
355
- username: 'john.doe@example.com',
356
- preset: 'passkey',
357
- challenge: 'your-base64url-encoded-challenge',
358
- authenticatorSelection: {
359
- authenticatorAttachment: 'platform',
360
- userVerification: 'required',
361
- },
362
- })
363
- .subscribe((result) => {
364
- console.log('Passkey registered:', result);
365
- });
366
- ```
367
-
368
- ## Server-Side Integration
369
-
370
- This library handles the client-side WebAuthn flow. You'll need a server-side implementation to:
371
-
372
- 1. **Generate challenges** (optional - library can generate them)
373
- 2. **Verify registration responses**
374
- 3. **Store public keys and credential metadata**
375
- 4. **Verify authentication responses**
376
-
377
- Popular server-side libraries:
378
-
379
- - **Node.js**: `@simplewebauthn/server`
380
- - **.NET**: `Fido2NetLib`
381
- - **Java**: `webauthn4j`
382
- - **Python**: `py_webauthn`
383
- - **Go**: `go-webauthn`
384
-
385
- ## Browser Support
386
-
387
- WebAuthn is supported in all modern browsers:
388
-
389
- - Chrome 67+
390
- - Firefox 60+
391
- - Safari 14+
392
- - Edge 18+
393
-
394
- Check current support at [caniuse.com/webauthn](https://caniuse.com/webauthn).
395
-
396
- ## Development
397
-
398
- ### Running unit tests
399
-
400
- Run `nx test ngx-webauthn` to execute the unit tests.
401
-
402
- ### Building the library
403
-
404
- Run `nx build ngx-webauthn` to build the library.
405
-
406
- ## Contributing
407
-
408
- Contributions are welcome! Please read our contributing guidelines and submit pull requests to our repository.
409
-
410
- ## License
411
-
412
- MIT License - see LICENSE file for details.
1
+ # ngx-webauthn
2
+
3
+ > โš ๏ธ **BETA SOFTWARE** โš ๏ธ
4
+ >
5
+ > This library is currently in beta. The API surface is subject to change in future versions.
6
+ > Please use with caution in production environments and be prepared to update your code
7
+ > when new versions are released.
8
+
9
+ A comprehensive Angular library that provides a clean, type-safe abstraction over the WebAuthn API. This library offers direct support for native WebAuthn types while providing an optional preset system for common scenarios.
10
+
11
+ ## Features
12
+
13
+ - ๐Ÿ” **Complete WebAuthn Support** - Full registration and authentication flows
14
+ - ๐Ÿ›ก๏ธ **Native Type Safety** - Direct support for WebAuthn types with full TypeScript support
15
+ - ๐ŸŽฏ **Flexible API** - Use native WebAuthn options directly or simplified presets
16
+ - ๐Ÿ“ฑ **Cross-Platform** - Works with platform authenticators (Face ID, Touch ID, Windows Hello)
17
+ - ๐Ÿ”‘ **Security Keys** - Support for external authenticators (YubiKey, etc.)
18
+ - โšก **RxJS Integration** - Observable-based API that fits naturally into Angular
19
+ - ๐Ÿงช **Well Tested** - Comprehensive unit test coverage
20
+ - ๐Ÿ“– **Browser Support Detection** - Check WebAuthn and platform authenticator availability
21
+
22
+ ## Installation
23
+
24
+ ```bash
25
+ npm install ngx-webauthn
26
+ ```
27
+
28
+ ## Quick Start
29
+
30
+ ### 1. Configure the Service
31
+
32
+ First, configure the WebAuthn service with your relying party information:
33
+
34
+ ```typescript
35
+ // main.ts (standalone app)
36
+ import { bootstrapApplication } from '@angular/platform-browser';
37
+ import { provideWebAuthn } from 'ngx-webauthn';
38
+ import { AppComponent } from './app/app.component';
39
+
40
+ bootstrapApplication(AppComponent, {
41
+ providers: [
42
+ provideWebAuthn(
43
+ { name: 'My App', id: 'myapp.com' }, // Required relying party config
44
+ { defaultTimeout: 30000 } // Optional overrides
45
+ ),
46
+ // ... other providers
47
+ ],
48
+ });
49
+ ```
50
+
51
+ ```typescript
52
+ // app.module.ts (module-based app)
53
+ import { NgModule } from '@angular/core';
54
+ import { BrowserModule } from '@angular/platform-browser';
55
+ import { WEBAUTHN_CONFIG, createWebAuthnConfig } from 'ngx-webauthn';
56
+
57
+ @NgModule({
58
+ imports: [BrowserModule],
59
+ providers: [
60
+ {
61
+ provide: WEBAUTHN_CONFIG,
62
+ useValue: createWebAuthnConfig({ name: 'My App', id: 'myapp.com' }, { defaultTimeout: 30000 }),
63
+ },
64
+ ],
65
+ // ...
66
+ })
67
+ export class AppModule {}
68
+ ```
69
+
70
+ ### 2. Use Native WebAuthn Types
71
+
72
+ The library provides direct support for standard WebAuthn types, giving you full control:
73
+
74
+ ```typescript
75
+ import { Component, inject } from '@angular/core';
76
+ import { WebAuthnService, WebAuthnError, WebAuthnErrorType } from 'ngx-webauthn';
77
+
78
+ @Component({
79
+ selector: 'app-auth',
80
+ template: `
81
+ <div>
82
+ <button (click)="register()" [disabled]="!isSupported">Register</button>
83
+ <button (click)="authenticate()" [disabled]="!isSupported">Authenticate</button>
84
+ <p *ngIf="!isSupported">WebAuthn is not supported in this browser</p>
85
+ </div>
86
+ `,
87
+ })
88
+ export class AuthComponent {
89
+ private webAuthnService = inject(WebAuthnService);
90
+
91
+ isSupported = this.webAuthnService.isSupported();
92
+
93
+ register() {
94
+ // Native WebAuthn creation options
95
+ const creationOptions: PublicKeyCredentialCreationOptions = {
96
+ rp: {
97
+ name: 'My App',
98
+ id: 'myapp.com',
99
+ },
100
+ user: {
101
+ id: new TextEncoder().encode('user123'),
102
+ name: 'john.doe@example.com',
103
+ displayName: 'John Doe',
104
+ },
105
+ challenge: crypto.getRandomValues(new Uint8Array(32)),
106
+ pubKeyCredParams: [
107
+ { type: 'public-key', alg: -7 }, // ES256
108
+ { type: 'public-key', alg: -257 }, // RS256
109
+ ],
110
+ authenticatorSelection: {
111
+ authenticatorAttachment: 'platform',
112
+ userVerification: 'required',
113
+ residentKey: 'required',
114
+ },
115
+ timeout: 60000,
116
+ attestation: 'direct',
117
+ };
118
+
119
+ this.webAuthnService.register(creationOptions).subscribe({
120
+ next: (result) => {
121
+ console.log('Registration successful:', result);
122
+ // Send result.credentialId, result.publicKey, etc. to your server
123
+ },
124
+ error: (error: WebAuthnError) => {
125
+ console.error('Registration failed:', error.message);
126
+ if (error.type === WebAuthnErrorType.USER_CANCELLED) {
127
+ // User cancelled or denied permission
128
+ }
129
+ },
130
+ });
131
+ }
132
+
133
+ authenticate() {
134
+ // Native WebAuthn request options
135
+ const requestOptions: PublicKeyCredentialRequestOptions = {
136
+ challenge: crypto.getRandomValues(new Uint8Array(32)),
137
+ allowCredentials: [
138
+ {
139
+ type: 'public-key',
140
+ id: new TextEncoder().encode('credential-id-from-registration'),
141
+ transports: ['internal'],
142
+ },
143
+ ],
144
+ userVerification: 'preferred',
145
+ timeout: 60000,
146
+ };
147
+
148
+ this.webAuthnService.authenticate(requestOptions).subscribe({
149
+ next: (result) => {
150
+ console.log('Authentication successful:', result);
151
+ // Send result to your server for verification
152
+ },
153
+ error: (error: WebAuthnError) => {
154
+ console.error('Authentication failed:', error.message);
155
+ },
156
+ });
157
+ }
158
+ }
159
+ ```
160
+
161
+ ### 3. Using JSON WebAuthn Options
162
+
163
+ The library also supports JSON-based WebAuthn options with base64url encoding:
164
+
165
+ ```typescript
166
+ register() {
167
+ // JSON WebAuthn creation options (base64url encoded)
168
+ const jsonOptions: PublicKeyCredentialCreationOptionsJSON = {
169
+ rp: {
170
+ name: 'My App',
171
+ id: 'myapp.com'
172
+ },
173
+ user: {
174
+ id: 'dXNlcjEyMw', // base64url encoded 'user123'
175
+ name: 'john.doe@example.com',
176
+ displayName: 'John Doe',
177
+ },
178
+ challenge: 'Y2hhbGxlbmdlMTIzNDU2Nzg5MA', // base64url encoded challenge
179
+ pubKeyCredParams: [
180
+ { type: 'public-key', alg: -7 },
181
+ { type: 'public-key', alg: -257 },
182
+ ],
183
+ authenticatorSelection: {
184
+ authenticatorAttachment: 'cross-platform',
185
+ userVerification: 'preferred',
186
+ residentKey: 'discouraged',
187
+ },
188
+ timeout: 60000,
189
+ attestation: 'none',
190
+ };
191
+
192
+ this.webAuthnService.register(jsonOptions).subscribe({
193
+ next: (result) => {
194
+ console.log('Registration successful:', result);
195
+ },
196
+ error: (error: WebAuthnError) => {
197
+ console.error('Registration failed:', error.message);
198
+ },
199
+ });
200
+ }
201
+ ```
202
+
203
+ ### 4. Alternative: Simplified Preset System
204
+
205
+ For convenience, the library includes an optional preset system for common scenarios:
206
+
207
+ ```typescript
208
+ register() {
209
+ this.webAuthnService
210
+ .register({
211
+ username: 'john.doe@example.com',
212
+ preset: 'passkey', // Easy preset configuration!
213
+ })
214
+ .subscribe({
215
+ next: (result) => {
216
+ console.log('Registration successful:', result);
217
+ // Send result.credentialId, result.publicKey, etc. to your server
218
+ },
219
+ error: (error: WebAuthnError) => {
220
+ console.error('Registration failed:', error.message);
221
+ if (error.type === WebAuthnErrorType.USER_CANCELLED) {
222
+ // User cancelled or denied permission
223
+ }
224
+ },
225
+ });
226
+ }
227
+
228
+ authenticate() {
229
+ this.webAuthnService
230
+ .authenticate({
231
+ preset: 'passkey', // Uses the same preset for consistency
232
+ // Optional: specify allowed credentials
233
+ allowCredentials: ['credential-id-from-registration'],
234
+ })
235
+ .subscribe({
236
+ next: (result) => {
237
+ console.log('Authentication successful:', result);
238
+ // Send result to your server for verification
239
+ },
240
+ error: (error: WebAuthnError) => {
241
+ console.error('Authentication failed:', error.message);
242
+ },
243
+ });
244
+ }
245
+ ```
246
+
247
+ ### 5. Check Browser Support
248
+
249
+ ```typescript
250
+ import { Component, OnInit, inject } from '@angular/core';
251
+ import { WebAuthnService } from 'ngx-webauthn';
252
+
253
+ @Component({
254
+ selector: 'app-support-check',
255
+ template: `
256
+ <div *ngIf="support">
257
+ <p>WebAuthn Support: {{ support.isSupported ? 'Yes' : 'No' }}</p>
258
+ <p>Platform Authenticator: {{ support.isPlatformAuthenticatorAvailable ? 'Available' : 'Not Available' }}</p>
259
+ <p>Supported Transports: {{ support.supportedTransports.join(', ') }}</p>
260
+ </div>
261
+ `,
262
+ })
263
+ export class SupportCheckComponent implements OnInit {
264
+ private webAuthnService = inject(WebAuthnService);
265
+ support: any;
266
+
267
+ ngOnInit() {
268
+ this.webAuthnService.getSupport().subscribe({
269
+ next: (support) => {
270
+ this.support = support;
271
+ },
272
+ error: (error) => {
273
+ console.error('Failed to check WebAuthn support:', error);
274
+ },
275
+ });
276
+ }
277
+ }
278
+ ```
279
+
280
+ ## Remote Server Integration
281
+
282
+ The library supports fetching WebAuthn options from remote endpoints, allowing your server to generate the options while the client handles the WebAuthn flow:
283
+
284
+ ### Configuration
285
+
286
+ First, configure the remote endpoints in your WebAuthn setup:
287
+
288
+ ```typescript
289
+ // main.ts (standalone app)
290
+ import { bootstrapApplication } from '@angular/platform-browser';
291
+ import { provideHttpClient } from '@angular/common/http';
292
+ import { provideWebAuthn } from 'ngx-webauthn';
293
+ import { AppComponent } from './app/app.component';
294
+
295
+ bootstrapApplication(AppComponent, {
296
+ providers: [
297
+ provideHttpClient(), // Required for remote functionality
298
+ provideWebAuthn(
299
+ { name: 'My App', id: 'myapp.com' },
300
+ {
301
+ remoteEndpoints: {
302
+ registration: 'https://api.myapp.com/webauthn/registration/options',
303
+ authentication: 'https://api.myapp.com/webauthn/authentication/options',
304
+ requestOptions: {
305
+ timeout: 10000, // Network timeout in milliseconds
306
+ },
307
+ },
308
+ }
309
+ ),
310
+ // ... other providers
311
+ ],
312
+ });
313
+ ```
314
+
315
+ ```typescript
316
+ // app.module.ts (module-based app)
317
+ import { NgModule } from '@angular/core';
318
+ import { BrowserModule } from '@angular/platform-browser';
319
+ import { HttpClientModule } from '@angular/common/http';
320
+ import { WEBAUTHN_CONFIG, createWebAuthnConfig } from 'ngx-webauthn';
321
+
322
+ @NgModule({
323
+ imports: [BrowserModule, HttpClientModule],
324
+ providers: [
325
+ {
326
+ provide: WEBAUTHN_CONFIG,
327
+ useValue: createWebAuthnConfig(
328
+ { name: 'My App', id: 'myapp.com' },
329
+ {
330
+ remoteEndpoints: {
331
+ registration: 'https://api.myapp.com/webauthn/registration/options',
332
+ authentication: 'https://api.myapp.com/webauthn/authentication/options',
333
+ requestOptions: { timeout: 10000 },
334
+ },
335
+ }
336
+ ),
337
+ },
338
+ ],
339
+ // ...
340
+ })
341
+ export class AppModule {}
342
+ ```
343
+
344
+ ### Remote Registration
345
+
346
+ Use `registerRemote()` to fetch creation options from your server:
347
+
348
+ ```typescript
349
+ import { Component, inject } from '@angular/core';
350
+ import { WebAuthnService } from 'ngx-webauthn';
351
+
352
+ @Component({
353
+ selector: 'app-auth',
354
+ template: ` <button (click)="registerWithServer()" [disabled]="!isSupported">Register with Server</button> `,
355
+ })
356
+ export class AuthComponent {
357
+ private webAuthnService = inject(WebAuthnService);
358
+ isSupported = this.webAuthnService.isSupported();
359
+
360
+ registerWithServer() {
361
+ // Simple registration request
362
+ this.webAuthnService
363
+ .registerRemote({
364
+ username: 'john.doe@example.com',
365
+ displayName: 'John Doe',
366
+ })
367
+ .subscribe({
368
+ next: (response) => {
369
+ console.log('Registration successful:', response);
370
+ // Send response.rawResponse to your server for verification
371
+ },
372
+ error: (error) => {
373
+ console.error('Registration failed:', error);
374
+ },
375
+ });
376
+ }
377
+ }
378
+ ```
379
+
380
+ ### Remote Authentication
381
+
382
+ Use `authenticateRemote()` to fetch request options from your server:
383
+
384
+ ```typescript
385
+ authenticateWithServer() {
386
+ // Simple authentication request
387
+ this.webAuthnService.authenticateRemote({
388
+ username: 'john.doe@example.com'
389
+ }).subscribe({
390
+ next: (response) => {
391
+ console.log('Authentication successful:', response);
392
+ // Send response.rawResponse to your server for verification
393
+ },
394
+ error: (error) => {
395
+ console.error('Authentication failed:', error);
396
+ }
397
+ });
398
+ }
399
+ ```
400
+
401
+ ### Custom Server Payloads
402
+
403
+ The library supports any payload structure your server requires:
404
+
405
+ ```typescript
406
+ // Define your custom payload types
407
+ interface MyRegistrationPayload {
408
+ tenantId: string;
409
+ department: string;
410
+ userId: string;
411
+ }
412
+
413
+ interface MyAuthenticationPayload {
414
+ sessionId: string;
415
+ context: 'web' | 'mobile';
416
+ }
417
+
418
+ // Use with type safety
419
+ this.webAuthnService
420
+ .registerRemote<MyRegistrationPayload>({
421
+ tenantId: 'acme-corp',
422
+ department: 'engineering',
423
+ userId: 'emp-12345',
424
+ })
425
+ .subscribe((response) => {
426
+ console.log('Registration complete:', response);
427
+ });
428
+
429
+ this.webAuthnService
430
+ .authenticateRemote<MyAuthenticationPayload>({
431
+ sessionId: 'session-abc123',
432
+ context: 'web',
433
+ })
434
+ .subscribe((response) => {
435
+ console.log('Authentication complete:', response);
436
+ });
437
+ ```
438
+
439
+ ### Server Endpoint Requirements
440
+
441
+ Your server endpoints should implement the following contracts:
442
+
443
+ #### Registration Endpoint
444
+
445
+ **Request:** `POST /webauthn/registration/options`
446
+
447
+ - **Body:** Any JSON payload your application needs
448
+ - **Response:** `PublicKeyCredentialCreationOptionsJSON`
449
+
450
+ ```javascript
451
+ // Example server response
452
+ {
453
+ "rp": {
454
+ "name": "My App",
455
+ "id": "myapp.com"
456
+ },
457
+ "user": {
458
+ "id": "dXNlci0xMjM0NQ", // base64url encoded user ID
459
+ "name": "john.doe@example.com",
460
+ "displayName": "John Doe"
461
+ },
462
+ "challenge": "Y2hhbGxlbmdlLWRhdGE", // base64url encoded challenge
463
+ "pubKeyCredParams": [
464
+ { "type": "public-key", "alg": -7 },
465
+ { "type": "public-key", "alg": -257 }
466
+ ],
467
+ "timeout": 60000,
468
+ "attestation": "none",
469
+ "authenticatorSelection": {
470
+ "userVerification": "preferred",
471
+ "residentKey": "preferred"
472
+ },
473
+ "excludeCredentials": [
474
+ {
475
+ "type": "public-key",
476
+ "id": "Y3JlZGVudGlhbC0xMjM", // base64url encoded credential ID
477
+ "transports": ["usb", "nfc"]
478
+ }
479
+ ]
480
+ }
481
+ ```
482
+
483
+ #### Authentication Endpoint
484
+
485
+ **Request:** `POST /webauthn/authentication/options`
486
+
487
+ - **Body:** Any JSON payload your application needs (can be empty `{}`)
488
+ - **Response:** `PublicKeyCredentialRequestOptionsJSON`
489
+
490
+ ```javascript
491
+ // Example server response
492
+ {
493
+ "challenge": "YXV0aC1jaGFsbGVuZ2U", // base64url encoded challenge
494
+ "timeout": 60000,
495
+ "userVerification": "preferred",
496
+ "allowCredentials": [
497
+ {
498
+ "type": "public-key",
499
+ "id": "Y3JlZGVudGlhbC0xMjM", // base64url encoded credential ID
500
+ "transports": ["usb", "nfc"]
501
+ }
502
+ ]
503
+ }
504
+ ```
505
+
506
+ ### Error Handling
507
+
508
+ Remote operations provide enhanced error context:
509
+
510
+ ```typescript
511
+ import { RemoteEndpointError, InvalidRemoteOptionsError } from 'ngx-webauthn';
512
+
513
+ this.webAuthnService.registerRemote(payload).subscribe({
514
+ error: (error) => {
515
+ if (error instanceof RemoteEndpointError) {
516
+ console.log('Server error:', error.context.status);
517
+ console.log('Endpoint:', error.context.url);
518
+ console.log('Operation:', error.context.operation);
519
+ } else if (error instanceof InvalidRemoteOptionsError) {
520
+ console.log('Server returned invalid options:', error.message);
521
+ }
522
+ },
523
+ });
524
+ ```
525
+
526
+ ### Authentication and Headers
527
+
528
+ Use Angular HTTP interceptors for authentication, CSRF tokens, and custom headers:
529
+
530
+ ```typescript
531
+ // auth.interceptor.ts
532
+ import { Injectable } from '@angular/core';
533
+ import { HttpInterceptor, HttpRequest, HttpHandler } from '@angular/common/http';
534
+
535
+ @Injectable()
536
+ export class AuthInterceptor implements HttpInterceptor {
537
+ intercept(req: HttpRequest<any>, next: HttpHandler) {
538
+ // Add authentication headers for WebAuthn endpoints
539
+ if (req.url.includes('/webauthn/')) {
540
+ const authReq = req.clone({
541
+ setHeaders: {
542
+ Authorization: `Bearer ${this.getAuthToken()}`,
543
+ 'X-CSRF-Token': this.getCsrfToken(),
544
+ },
545
+ });
546
+ return next.handle(authReq);
547
+ }
548
+ return next.handle(req);
549
+ }
550
+
551
+ private getAuthToken(): string {
552
+ // Your auth token logic
553
+ return 'your-auth-token';
554
+ }
555
+
556
+ private getCsrfToken(): string {
557
+ // Your CSRF token logic
558
+ return 'your-csrf-token';
559
+ }
560
+ }
561
+
562
+ // main.ts
563
+ import { provideHttpClient, withInterceptors } from '@angular/common/http';
564
+
565
+ bootstrapApplication(AppComponent, {
566
+ providers: [
567
+ provideHttpClient(withInterceptors([authInterceptor])),
568
+ // ... other providers
569
+ ],
570
+ });
571
+ ```
572
+
573
+ ### Security Considerations
574
+
575
+ - **HTTPS Required:** All remote endpoints should use HTTPS in production
576
+ - **Authentication:** Protect your endpoints with proper authentication
577
+ - **Validation:** Always validate the server response structure
578
+ - **CSRF Protection:** Use CSRF tokens for state-changing operations
579
+ - **Rate Limiting:** Implement rate limiting on your WebAuthn endpoints
580
+
581
+ ## Native WebAuthn Types Support
582
+
583
+ The library provides comprehensive support for standard WebAuthn types:
584
+
585
+ ### Registration with Native Types
586
+
587
+ ```typescript
588
+ // Native ArrayBuffer-based creation options
589
+ const nativeOptions: PublicKeyCredentialCreationOptions = {
590
+ rp: { name: 'My App', id: 'myapp.com' },
591
+ user: {
592
+ id: new TextEncoder().encode('user-id'),
593
+ name: 'user@example.com',
594
+ displayName: 'User Name',
595
+ },
596
+ challenge: crypto.getRandomValues(new Uint8Array(32)),
597
+ pubKeyCredParams: [
598
+ { type: 'public-key', alg: -7 }, // ES256
599
+ { type: 'public-key', alg: -257 }, // RS256
600
+ ],
601
+ excludeCredentials: [
602
+ {
603
+ type: 'public-key',
604
+ id: new TextEncoder().encode('existing-credential-id'),
605
+ transports: ['usb', 'nfc'],
606
+ },
607
+ ],
608
+ authenticatorSelection: {
609
+ authenticatorAttachment: 'platform',
610
+ userVerification: 'required',
611
+ residentKey: 'required',
612
+ },
613
+ timeout: 60000,
614
+ attestation: 'direct',
615
+ };
616
+
617
+ this.webAuthnService.register(nativeOptions).subscribe((result) => {
618
+ console.log('Registration complete:', result);
619
+ });
620
+ ```
621
+
622
+ ### Authentication with Native Types
623
+
624
+ ```typescript
625
+ // Native ArrayBuffer-based request options
626
+ const nativeRequest: PublicKeyCredentialRequestOptions = {
627
+ challenge: crypto.getRandomValues(new Uint8Array(32)),
628
+ allowCredentials: [
629
+ {
630
+ type: 'public-key',
631
+ id: new TextEncoder().encode('credential-id'),
632
+ transports: ['internal', 'usb'],
633
+ },
634
+ ],
635
+ userVerification: 'preferred',
636
+ timeout: 60000,
637
+ };
638
+
639
+ this.webAuthnService.authenticate(nativeRequest).subscribe((result) => {
640
+ console.log('Authentication complete:', result);
641
+ });
642
+ ```
643
+
644
+ ### JSON WebAuthn Options
645
+
646
+ ```typescript
647
+ // JSON base64url-encoded creation options
648
+ const jsonOptions: PublicKeyCredentialCreationOptionsJSON = {
649
+ rp: { name: 'My App', id: 'myapp.com' },
650
+ user: {
651
+ id: 'dXNlci1pZA', // base64url encoded 'user-id'
652
+ name: 'user@example.com',
653
+ displayName: 'User Name',
654
+ },
655
+ challenge: 'Y2hhbGxlbmdlLWRhdGE', // base64url encoded challenge
656
+ pubKeyCredParams: [
657
+ { type: 'public-key', alg: -7 },
658
+ { type: 'public-key', alg: -257 },
659
+ ],
660
+ excludeCredentials: [
661
+ {
662
+ type: 'public-key',
663
+ id: 'ZXhpc3RpbmctY3JlZGVudGlhbC1pZA', // base64url encoded
664
+ transports: ['usb', 'nfc'],
665
+ },
666
+ ],
667
+ authenticatorSelection: {
668
+ authenticatorAttachment: 'platform',
669
+ userVerification: 'required',
670
+ residentKey: 'required',
671
+ },
672
+ timeout: 60000,
673
+ attestation: 'direct',
674
+ };
675
+
676
+ this.webAuthnService.register(jsonOptions).subscribe((result) => {
677
+ console.log('Registration complete:', result);
678
+ });
679
+ ```
680
+
681
+ ## Features Overview
682
+
683
+ ### ๐ŸŽฏ **Optional Preset System**
684
+
685
+ The library includes intelligent presets for common WebAuthn use cases:
686
+
687
+ - **`passkey`** - Modern passwordless authentication with credential syncing
688
+ - **`externalSecurityKey`** - Traditional 2FA with hardware security keys
689
+ - **`platformAuthenticator`** - High-security single-device authentication
690
+
691
+ ```typescript
692
+ // Simple preset usage
693
+ this.webAuthnService.register({ username: 'user@example.com', preset: 'passkey' });
694
+
695
+ // Preset with custom overrides (native WebAuthn options)
696
+ this.webAuthnService.register({
697
+ username: 'user@example.com',
698
+ preset: 'passkey',
699
+ authenticatorSelection: { userVerification: 'required' },
700
+ timeout: 30000,
701
+ });
702
+ ```
703
+
704
+ ### ๐Ÿ”ง **Enhanced Configuration**
705
+
706
+ The new configuration system ensures proper security defaults:
707
+
708
+ ```typescript
709
+ interface WebAuthnConfig {
710
+ relyingParty: {
711
+ name: string; // Required: Your app name
712
+ id?: string; // Optional: Your domain
713
+ };
714
+ defaultTimeout?: number; // Default: 60000ms
715
+ defaultAlgorithms?: PublicKeyCredentialParameters[];
716
+ enforceUserVerification?: boolean; // Default: false
717
+ defaultAttestation?: AttestationConveyancePreference;
718
+ defaultAuthenticatorSelection?: AuthenticatorSelectionCriteria;
719
+ }
720
+ ```
721
+
722
+ ### ๐Ÿ”„ **Flexible Input Types**
723
+
724
+ Supports multiple input formats for maximum flexibility:
725
+
726
+ ```typescript
727
+ // Native WebAuthn options (recommended for full control)
728
+ service.register(nativeCreationOptions);
729
+
730
+ // JSON WebAuthn options (base64url strings)
731
+ service.register(jsonCreationOptions);
732
+
733
+ // High-level preset config (convenience)
734
+ service.register({ username: 'user@example.com', preset: 'passkey' });
735
+ ```
736
+
737
+ ## API Reference
738
+
739
+ ### WebAuthnService
740
+
741
+ #### Methods
742
+
743
+ ##### `isSupported(): boolean`
744
+
745
+ Synchronously checks if WebAuthn is supported in the current browser.
746
+
747
+ ##### `getSupport(): Observable<WebAuthnSupport>`
748
+
749
+ Returns detailed information about WebAuthn support including platform authenticator availability.
750
+
751
+ ##### `register(input: RegisterInput): Observable<RegistrationResponse>`
752
+
753
+ Registers a new WebAuthn credential. Accepts native WebAuthn options, JSON options, or high-level config objects.
754
+
755
+ ```typescript
756
+ type RegisterInput = PublicKeyCredentialCreationOptions | PublicKeyCredentialCreationOptionsJSON | RegisterConfig;
757
+ ```
758
+
759
+ ##### `authenticate(input: AuthenticateInput): Observable<AuthenticationResponse>`
760
+
761
+ Authenticates using an existing WebAuthn credential. Accepts native WebAuthn options, JSON options, or high-level config objects.
762
+
763
+ ```typescript
764
+ type AuthenticateInput = PublicKeyCredentialRequestOptions | PublicKeyCredentialRequestOptionsJSON | AuthenticateConfig;
765
+ ```
766
+
767
+ ### Response Types
768
+
769
+ #### RegistrationResponse
770
+
771
+ ```typescript
772
+ interface RegistrationResponse {
773
+ success: boolean;
774
+ credentialId: string; // Base64url encoded
775
+ publicKey?: string; // Base64url encoded (if available)
776
+ transports?: AuthenticatorTransport[];
777
+ rawResponse?: WebAuthnRegistrationResult; // Complete WebAuthn data for advanced usage
778
+ }
779
+ ```
780
+
781
+ #### AuthenticationResponse
782
+
783
+ ```typescript
784
+ interface AuthenticationResponse {
785
+ success: boolean;
786
+ credentialId: string; // Base64url encoded
787
+ userHandle?: string; // Base64url encoded
788
+ rawResponse?: WebAuthnAuthenticationResult; // Complete WebAuthn data for advanced usage
789
+ }
790
+ ```
791
+
792
+ #### WebAuthnRegistrationResult
793
+
794
+ ```typescript
795
+ interface WebAuthnRegistrationResult {
796
+ credentialId: string; // Base64url encoded
797
+ publicKey: string; // Base64url encoded
798
+ attestationObject: string; // Base64url encoded
799
+ clientDataJSON: string; // Base64url encoded
800
+ transports?: AuthenticatorTransport[];
801
+ }
802
+ ```
803
+
804
+ #### WebAuthnAuthenticationResult
805
+
806
+ ```typescript
807
+ interface WebAuthnAuthenticationResult {
808
+ credentialId: string; // Base64url encoded
809
+ authenticatorData: string; // Base64url encoded
810
+ clientDataJSON: string; // Base64url encoded
811
+ signature: string; // Base64url encoded
812
+ userHandle?: string; // Base64url encoded
813
+ }
814
+ ```
815
+
816
+ ### Error Handling
817
+
818
+ The library provides structured error handling through the `WebAuthnError` class:
819
+
820
+ ```typescript
821
+ enum WebAuthnErrorType {
822
+ NOT_SUPPORTED = 'NOT_SUPPORTED',
823
+ NOT_ALLOWED = 'NOT_ALLOWED',
824
+ INVALID_STATE = 'INVALID_STATE',
825
+ CONSTRAINT = 'CONSTRAINT',
826
+ SECURITY = 'SECURITY',
827
+ NETWORK = 'NETWORK',
828
+ ABORT = 'ABORT',
829
+ TIMEOUT = 'TIMEOUT',
830
+ ENCODING = 'ENCODING',
831
+ UNKNOWN = 'UNKNOWN',
832
+ }
833
+
834
+ class WebAuthnError extends Error {
835
+ constructor(public readonly type: WebAuthnErrorType, message: string, public readonly originalError?: Error);
836
+ }
837
+ ```
838
+
839
+ ## Advanced Usage
840
+
841
+ ### Config with Custom Challenge
842
+
843
+ ```typescript
844
+ // High-level config with custom challenge
845
+ this.webAuthnService
846
+ .register({
847
+ username: 'john.doe@example.com',
848
+ preset: 'passkey',
849
+ challenge: 'your-base64url-encoded-challenge',
850
+ authenticatorSelection: {
851
+ authenticatorAttachment: 'platform',
852
+ userVerification: 'required',
853
+ },
854
+ })
855
+ .subscribe((result) => {
856
+ console.log('Passkey registered:', result);
857
+ });
858
+ ```
859
+
860
+ ### Preset System (Optional)
861
+
862
+ #### RegisterConfig
863
+
864
+ ```typescript
865
+ interface RegisterConfig {
866
+ username: string; // Required: username for the credential
867
+ preset?: 'passkey' | 'externalSecurityKey' | 'platformAuthenticator';
868
+ displayName?: string; // Defaults to username
869
+ rp?: { name: string; id?: string }; // Relying party info
870
+ challenge?: string | Uint8Array; // Auto-generated if not provided
871
+ timeout?: number; // Defaults to 60000ms
872
+ // ... other native WebAuthn options as overrides
873
+ }
874
+ ```
875
+
876
+ #### AuthenticateConfig
877
+
878
+ ```typescript
879
+ interface AuthenticateConfig {
880
+ username?: string; // Optional username hint
881
+ preset?: 'passkey' | 'externalSecurityKey' | 'platformAuthenticator';
882
+ challenge?: string | Uint8Array; // Auto-generated if not provided
883
+ timeout?: number; // Defaults to 60000ms
884
+ allowCredentials?: string[] | PublicKeyCredentialDescriptor[];
885
+ // ... other native WebAuthn options as overrides
886
+ }
887
+ ```
888
+
889
+ ## Server-Side Integration
890
+
891
+ This library handles the client-side WebAuthn flow. You'll need a server-side implementation to:
892
+
893
+ 1. **Generate challenges** (optional - library can generate them)
894
+ 2. **Verify registration responses**
895
+ 3. **Store public keys and credential metadata**
896
+ 4. **Verify authentication responses**
897
+
898
+ Popular server-side libraries:
899
+
900
+ - **Node.js**: `@simplewebauthn/server`
901
+ - **.NET**: `Fido2NetLib`
902
+ - **Java**: `webauthn4j`
903
+ - **Python**: `py_webauthn`
904
+ - **Go**: `go-webauthn`
905
+
906
+ ## Development
907
+
908
+ ### Running unit tests
909
+
910
+ Run `nx test ngx-webauthn` to execute the unit tests.
911
+
912
+ ### Building the library
913
+
914
+ Run `nx build ngx-webauthn` to build the library.
915
+
916
+ ## Contributing
917
+
918
+ Contributions are welcome! Please read our contributing guidelines and submit pull requests to our repository.
919
+
920
+ ## License
921
+
922
+ MIT License - see LICENSE file for details.