@xenterprises/fastify-xauth-jwks 1.0.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.
@@ -0,0 +1,545 @@
1
+ # Configuration Reference - xAuthJWSK
2
+
3
+ Complete guide to all configuration options for path-based JWT/JWKS validation.
4
+
5
+ ## Table of Contents
6
+
7
+ 1. [Plugin Options](#plugin-options)
8
+ 2. [Path Configuration](#path-configuration)
9
+ 3. [Configuration Examples](#configuration-examples)
10
+ 4. [Environment Variables](#environment-variables)
11
+ 5. [Production Checklist](#production-checklist)
12
+
13
+ ---
14
+
15
+ ## Plugin Options
16
+
17
+ The xAuthJWSK plugin accepts a single option object:
18
+
19
+ ```javascript
20
+ await fastify.register(xAuthJWSK, {
21
+ paths: {
22
+ // Path configurations go here
23
+ }
24
+ });
25
+ ```
26
+
27
+ ### Root Options
28
+
29
+ | Option | Type | Required | Description |
30
+ |--------|------|----------|-------------|
31
+ | `paths` | `Object` | Yes | Object mapping path names to path configurations |
32
+
33
+ ---
34
+
35
+ ## Path Configuration
36
+
37
+ Each path in the `paths` object defines a protected route group with its own JWKS provider.
38
+
39
+ ### Basic Structure
40
+
41
+ ```javascript
42
+ paths: {
43
+ [pathName]: {
44
+ // Required
45
+ pathPattern: string,
46
+ [jwksUrl | jwksData]: one required,
47
+
48
+ // Optional
49
+ active: boolean,
50
+ excludedPaths: string[],
51
+ enablePayloadCache: boolean,
52
+ payloadCacheTTL: number,
53
+ jwksCacheMaxAge: number,
54
+ jwksCooldownDuration: number,
55
+ }
56
+ }
57
+ ```
58
+
59
+ ### Configuration Properties
60
+
61
+ #### `pathPattern` (required, string)
62
+
63
+ The URL path prefix to protect. All routes matching this pattern will require authentication.
64
+
65
+ **Examples:**
66
+ ```javascript
67
+ pathPattern: "/admin" // Protects /admin and /admin/*
68
+ pathPattern: "/api/v1" // Protects /api/v1 and /api/v1/*
69
+ pathPattern: "/portal" // Protects /portal and /portal/*
70
+ ```
71
+
72
+ #### `jwksUrl` (required*, string)
73
+
74
+ Remote JWKS endpoint URL for production use. Fetches public keys from a remote server.
75
+
76
+ **When to use:**
77
+ - Production environments
78
+ - Multi-tenant systems
79
+ - Third-party authentication providers
80
+ - Key rotation needed without server restart
81
+
82
+ **Example:**
83
+ ```javascript
84
+ jwksUrl: "https://auth.example.com/.well-known/jwks.json"
85
+ ```
86
+
87
+ **Note:** Cannot be used simultaneously with `jwksData`. Choose one.
88
+
89
+ #### `jwksData` (required*, object)
90
+
91
+ Local JWKS data for development/testing or self-contained services. Can be either:
92
+ 1. Full JWKS object: `{ keys: [...] }`
93
+ 2. Single JWK key: `{ kty: "RSA", ... }`
94
+
95
+ **When to use:**
96
+ - Development and local testing
97
+ - Self-contained microservices
98
+ - Both signing and validating tokens
99
+ - Avoiding external dependencies
100
+
101
+ **Example - Full JWKS:**
102
+ ```javascript
103
+ import jwks from './keys.json' assert { type: 'json' };
104
+
105
+ jwksData: jwks // { keys: [{...}, {...}] }
106
+ ```
107
+
108
+ **Example - Single JWK (for signing + validation):**
109
+ ```javascript
110
+ import { exportJWK, generateKeyPair } from 'jose';
111
+
112
+ const { publicKey } = await generateKeyPair('RS256');
113
+ const publicJwk = await exportJWK(publicKey);
114
+
115
+ jwksData: {
116
+ ...publicJwk,
117
+ use: 'sig',
118
+ alg: 'RS256',
119
+ kid: 'my-key-id'
120
+ }
121
+ ```
122
+
123
+ **Note:** Cannot be used simultaneously with `jwksUrl`. Choose one.
124
+
125
+ #### `active` (optional, boolean, default: `true`)
126
+
127
+ Whether this path configuration is active. Set to `false` to disable without removing configuration.
128
+
129
+ **Example:**
130
+ ```javascript
131
+ active: false // This path won't be registered
132
+ ```
133
+
134
+ #### `excludedPaths` (optional, string[], default: `[]`)
135
+
136
+ Routes under this path pattern that do NOT require authentication.
137
+
138
+ **Use for:**
139
+ - Health checks: `/health`, `/status`
140
+ - Public documentation: `/docs`, `/swagger`
141
+ - Public endpoints: `/public`, `/info`
142
+
143
+ **Example:**
144
+ ```javascript
145
+ excludedPaths: ["/health", "/status", "/docs"]
146
+
147
+ // These routes do NOT require tokens:
148
+ // GET /admin/health ✅ No token needed
149
+ // GET /admin/status ✅ No token needed
150
+ // GET /admin/docs ✅ No token needed
151
+ // GET /admin/dashboard ❌ Token required
152
+ ```
153
+
154
+ #### `enablePayloadCache` (optional, boolean, default: `true`)
155
+
156
+ Whether to cache decoded JWT payloads to reduce verification overhead.
157
+
158
+ **Effect:**
159
+ - `true`: Tokens cached for `payloadCacheTTL` milliseconds (faster, ~90% reduction)
160
+ - `false`: Every token is verified and decoded (slower, fresh on every request)
161
+
162
+ **When to disable:**
163
+ - Permissions changing frequently
164
+ - Immediate revocation needed
165
+ - Low-traffic endpoints
166
+
167
+ **Example:**
168
+ ```javascript
169
+ enablePayloadCache: true // Default - recommended
170
+ ```
171
+
172
+ #### `payloadCacheTTL` (optional, number, default: `300000`)
173
+
174
+ JWT payload cache time-to-live in milliseconds (only if `enablePayloadCache: true`).
175
+
176
+ **Common values:**
177
+ - `60000` - 1 minute (very short, frequent refreshes)
178
+ - `300000` - 5 minutes (default, good balance)
179
+ - `600000` - 10 minutes (longer cache, better performance)
180
+ - `1800000` - 30 minutes (very long, stable services)
181
+
182
+ **Example:**
183
+ ```javascript
184
+ payloadCacheTTL: 300000 // 5 minutes
185
+ ```
186
+
187
+ **Recommendation:** Match to your JWT expiration time (usually 5-60 minutes).
188
+
189
+ #### `jwksCacheMaxAge` (optional, number, default: `1800000`)
190
+
191
+ JWKS cache time-to-live in milliseconds (only for remote `jwksUrl`).
192
+
193
+ **When JWKS is cached:**
194
+ - Keys are fetched once, then reused
195
+ - Reduces external API calls
196
+ - Faster token verification
197
+
198
+ **When to adjust:**
199
+ - Frequent key rotation: Lower value (60000 = 1 minute)
200
+ - Stable keys: Higher value (3600000 = 1 hour)
201
+
202
+ **Common values:**
203
+ - `300000` - 5 minutes (frequent rotation)
204
+ - `1800000` - 30 minutes (default, balanced)
205
+ - `3600000` - 1 hour (stable production keys)
206
+
207
+ **Example:**
208
+ ```javascript
209
+ jwksCacheMaxAge: 1800000 // 30 minutes
210
+ ```
211
+
212
+ **Note:** Only applies to `jwksUrl` (remote), not `jwksData` (local).
213
+
214
+ #### `jwksCooldownDuration` (optional, number, default: `60000`)
215
+
216
+ Cooldown between JWKS refetch attempts after a key mismatch (only for `jwksUrl`).
217
+
218
+ **When used:**
219
+ - Token signed with unknown `kid` (key ID)
220
+ - Plugin will wait before retrying
221
+
222
+ **Example:**
223
+ ```javascript
224
+ jwksCooldownDuration: 60000 // 1 minute between refetches
225
+ ```
226
+
227
+ ---
228
+
229
+ ## Configuration Examples
230
+
231
+ ### Example 1: Simple Development Setup
232
+
233
+ ```javascript
234
+ import Fastify from 'fastify';
235
+ import xAuthJWSK from '@xenterprises/fastify-xauth-jwks';
236
+ import localJwks from './keys.json' assert { type: 'json' };
237
+
238
+ const fastify = Fastify();
239
+
240
+ await fastify.register(xAuthJWSK, {
241
+ paths: {
242
+ api: {
243
+ pathPattern: "/api",
244
+ jwksData: localJwks,
245
+ excludedPaths: ["/health", "/docs"]
246
+ }
247
+ }
248
+ });
249
+
250
+ fastify.get('/api/data', (request) => ({ data: 'sensitive' }));
251
+ await fastify.listen({ port: 3000 });
252
+ ```
253
+
254
+ ### Example 2: Production with Remote JWKS
255
+
256
+ ```javascript
257
+ await fastify.register(xAuthJWSK, {
258
+ paths: {
259
+ admin: {
260
+ pathPattern: "/admin",
261
+ jwksUrl: "https://auth.example.com/.well-known/jwks.json",
262
+ jwksCacheMaxAge: 3600000, // 1 hour
263
+ jwksCooldownDuration: 60000 // 1 minute
264
+ },
265
+ api: {
266
+ pathPattern: "/api",
267
+ jwksUrl: "https://auth.example.com/.well-known/jwks.json",
268
+ payloadCacheTTL: 300000, // 5 minutes
269
+ enablePayloadCache: true
270
+ }
271
+ }
272
+ });
273
+ ```
274
+
275
+ ### Example 3: Self-Signed Token Service
276
+
277
+ ```javascript
278
+ import { generateKeyPair, exportJWK, exportPKCS8 } from 'jose';
279
+
280
+ // Generate keys
281
+ const { publicKey, privateKey } = await generateKeyPair('RS256');
282
+ const publicJwk = await exportJWK(publicKey);
283
+ const privateKeyPem = await exportPKCS8(privateKey);
284
+
285
+ // Register plugin with public key for validation
286
+ await fastify.register(xAuthJWSK, {
287
+ paths: {
288
+ api: {
289
+ pathPattern: "/api",
290
+ jwksData: {
291
+ ...publicJwk,
292
+ use: 'sig',
293
+ alg: 'RS256',
294
+ kid: 'self-signed-key'
295
+ }
296
+ }
297
+ }
298
+ });
299
+
300
+ // Use private key for signing (in application code)
301
+ fastify.post('/login', async (request) => {
302
+ const token = await new jose.SignJWT({
303
+ sub: user.id,
304
+ email: user.email
305
+ })
306
+ .setProtectedHeader({ alg: 'RS256', kid: 'self-signed-key' })
307
+ .sign(privateKey);
308
+
309
+ return { token };
310
+ });
311
+ ```
312
+
313
+ ### Example 4: Multiple Paths with Fallback
314
+
315
+ ```javascript
316
+ const config = {
317
+ paths: {
318
+ admin: {
319
+ pathPattern: "/admin",
320
+ // Use environment variable or local fallback
321
+ ...(process.env.ADMIN_JWKS_URL
322
+ ? { jwksUrl: process.env.ADMIN_JWKS_URL }
323
+ : { jwksData: localJwks.admin }
324
+ ),
325
+ enablePayloadCache: true,
326
+ payloadCacheTTL: 300000
327
+ },
328
+ portal: {
329
+ pathPattern: "/portal",
330
+ ...(process.env.PORTAL_JWKS_URL
331
+ ? { jwksUrl: process.env.PORTAL_JWKS_URL }
332
+ : { jwksData: localJwks.portal }
333
+ ),
334
+ excludedPaths: ["/public"],
335
+ enablePayloadCache: true
336
+ },
337
+ partner: {
338
+ pathPattern: "/partner",
339
+ jwksUrl: process.env.PARTNER_JWKS_URL,
340
+ enablePayloadCache: false // Refresh every request
341
+ }
342
+ }
343
+ };
344
+
345
+ await fastify.register(xAuthJWSK, config);
346
+ ```
347
+
348
+ ### Example 5: High-Traffic Production Setup
349
+
350
+ ```javascript
351
+ await fastify.register(xAuthJWSK, {
352
+ paths: {
353
+ api: {
354
+ pathPattern: "/api",
355
+ jwksUrl: "https://auth.example.com/.well-known/jwks.json",
356
+ // JWKS optimization
357
+ jwksCacheMaxAge: 3600000, // 1 hour cache
358
+ jwksCooldownDuration: 60000, // 1 min between refetches
359
+ // Token cache optimization
360
+ enablePayloadCache: true,
361
+ payloadCacheTTL: 600000, // 10 min token cache
362
+ // Excluded endpoints
363
+ excludedPaths: ["/health", "/status", "/metrics"]
364
+ }
365
+ }
366
+ });
367
+ ```
368
+
369
+ ---
370
+
371
+ ## Environment Variables
372
+
373
+ Common patterns for environment-based configuration:
374
+
375
+ ```bash
376
+ # Development
377
+ USE_LOCAL_JWKS=true
378
+ LOCAL_JWKS_PATH=./keys.json
379
+
380
+ # Production
381
+ ADMIN_JWKS_URL=https://auth.example.com/admin/.well-known/jwks.json
382
+ PORTAL_JWKS_URL=https://auth.example.com/portal/.well-known/jwks.json
383
+ PARTNER_JWKS_URL=https://auth.example.com/partner/.well-known/jwks.json
384
+
385
+ # Cache tuning
386
+ JWKS_CACHE_MAX_AGE=3600000
387
+ PAYLOAD_CACHE_TTL=300000
388
+ ```
389
+
390
+ **Usage:**
391
+ ```javascript
392
+ await fastify.register(xAuthJWSK, {
393
+ paths: {
394
+ admin: {
395
+ pathPattern: "/admin",
396
+ ...(process.env.USE_LOCAL_JWKS === 'true'
397
+ ? { jwksData: localJwks }
398
+ : { jwksUrl: process.env.ADMIN_JWKS_URL }
399
+ ),
400
+ jwksCacheMaxAge: Number(process.env.JWKS_CACHE_MAX_AGE) || 1800000,
401
+ payloadCacheTTL: Number(process.env.PAYLOAD_CACHE_TTL) || 300000
402
+ }
403
+ }
404
+ });
405
+ ```
406
+
407
+ ---
408
+
409
+ ## Production Checklist
410
+
411
+ ### Before Deploying to Production
412
+
413
+ - [ ] **Use `jwksUrl`** - Not local `jwksData`
414
+ - [ ] **Enable caching** - `enablePayloadCache: true` (default)
415
+ - [ ] **Set JWKS cache high** - `jwksCacheMaxAge: 3600000` (1 hour typical)
416
+ - [ ] **Configure TTL appropriately** - Match your token expiration time
417
+ - [ ] **Exclude health endpoints** - `excludedPaths: ["/health", "/status"]`
418
+ - [ ] **Test key rotation** - Verify keys update correctly
419
+ - [ ] **Monitor cache stats** - Use `getPayloadCacheStats()` regularly
420
+ - [ ] **Set up alerts** - Watch for authentication failures
421
+ - [ ] **Load test** - Verify performance with caching enabled
422
+ - [ ] **Use HTTPS** - Always use TLS in production
423
+ - [ ] **Store secrets securely** - Use environment variables or vaults
424
+ - [ ] **Implement token refresh** - Short-lived access tokens + refresh tokens
425
+
426
+ ### Monitoring
427
+
428
+ ```javascript
429
+ // Get cache statistics
430
+ const stats = fastify.xAuth.validators.admin.getPayloadCacheStats();
431
+ console.log(stats);
432
+ // { size: 245, enabled: true, ttl: 300000 }
433
+
434
+ // Monitor hit rate
435
+ // cache.size = current tokens in cache
436
+ // If size grows constantly, increase payloadCacheTTL
437
+ // If size stays low, decrease (or monitor if legitimate)
438
+ ```
439
+
440
+ ### Performance Tuning
441
+
442
+ | Scenario | Setting |
443
+ |----------|---------|
444
+ | **High request volume** | Increase `payloadCacheTTL` to 600000+ |
445
+ | **Frequent key rotation** | Decrease `jwksCacheMaxAge` to 300000 |
446
+ | **Real-time permission changes** | Set `enablePayloadCache: false` |
447
+ | **Stable JWKS keys** | Increase `jwksCacheMaxAge` to 3600000 |
448
+
449
+ ---
450
+
451
+ ## Common Configuration Patterns
452
+
453
+ ### Pattern 1: Minimal Configuration
454
+
455
+ ```javascript
456
+ await fastify.register(xAuthJWSK, {
457
+ paths: {
458
+ api: {
459
+ pathPattern: "/api",
460
+ jwksUrl: "https://auth.example.com/.well-known/jwks.json"
461
+ }
462
+ }
463
+ });
464
+ // Uses all defaults: 30 min JWKS cache, 5 min token cache, enabled
465
+ ```
466
+
467
+ ### Pattern 2: Development vs Production
468
+
469
+ ```javascript
470
+ const isDevelopment = process.env.NODE_ENV === 'development';
471
+
472
+ await fastify.register(xAuthJWSK, {
473
+ paths: {
474
+ api: {
475
+ pathPattern: "/api",
476
+ ...(isDevelopment
477
+ ? { jwksData: require('./keys.json') }
478
+ : { jwksUrl: process.env.JWKS_URL }
479
+ ),
480
+ enablePayloadCache: !isDevelopment
481
+ }
482
+ }
483
+ });
484
+ ```
485
+
486
+ ### Pattern 3: Strict Admin + Relaxed User API
487
+
488
+ ```javascript
489
+ await fastify.register(xAuthJWSK, {
490
+ paths: {
491
+ admin: {
492
+ pathPattern: "/admin",
493
+ jwksUrl: "https://auth.example.com/.well-known/jwks.json",
494
+ enablePayloadCache: false // No cache - strict/fresh
495
+ },
496
+ api: {
497
+ pathPattern: "/api",
498
+ jwksUrl: "https://auth.example.com/.well-known/jwks.json",
499
+ payloadCacheTTL: 600000 // 10 min cache - relaxed
500
+ }
501
+ }
502
+ });
503
+ ```
504
+
505
+ ---
506
+
507
+ ## Configuration Validation
508
+
509
+ The plugin validates configuration on registration:
510
+
511
+ ```javascript
512
+ // ❌ Missing both jwksUrl and jwksData
513
+ paths: {
514
+ api: {
515
+ pathPattern: "/api"
516
+ // Error: At least one of jwksUrl or jwksData is required
517
+ }
518
+ }
519
+
520
+ // ❌ Both jwksUrl and jwksData specified
521
+ paths: {
522
+ api: {
523
+ pathPattern: "/api",
524
+ jwksUrl: "https://...",
525
+ jwksData: { ... }
526
+ // Error: Cannot use both jwksUrl and jwksData
527
+ }
528
+ }
529
+
530
+ // ❌ Missing pathPattern
531
+ paths: {
532
+ api: {
533
+ jwksUrl: "https://..."
534
+ // Error: pathPattern is required
535
+ }
536
+ }
537
+
538
+ // ✅ Valid configuration
539
+ paths: {
540
+ api: {
541
+ pathPattern: "/api",
542
+ jwksUrl: "https://auth.example.com/.well-known/jwks.json"
543
+ }
544
+ }
545
+ ```