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 +922 -412
- package/fesm2022/ngx-webauthn.mjs +1117 -190
- package/fesm2022/ngx-webauthn.mjs.map +1 -1
- package/index.d.ts +841 -165
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -1,412 +1,922 @@
|
|
|
1
|
-
# ngx-webauthn
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
##
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
//
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
],
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
},
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
###
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
```typescript
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
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.
|