@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.
- package/AUTHENTICATION_EXAMPLE.md +453 -0
- package/CACHING.md +282 -0
- package/CONFIGURATION.md +545 -0
- package/DEVELOPMENT.md +385 -0
- package/JOSE_UTILITIES.md +204 -0
- package/KEYS_GENERATION.md +359 -0
- package/QUICK_START.md +334 -0
- package/README.md +73 -0
- package/package.json +44 -0
- package/server/app.js +370 -0
- package/server/example-jwks.json +12 -0
- package/server/generate-demo-token.js +232 -0
- package/src/index.js +9 -0
- package/src/services/pathValidator.js +175 -0
- package/src/utils/index.js +145 -0
- package/src/xAuth.js +36 -0
- package/test/integration.test.js +259 -0
- package/test/utils.test.js +195 -0
- package/test/xAuth.test.js +439 -0
package/CONFIGURATION.md
ADDED
|
@@ -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
|
+
```
|