@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/DEVELOPMENT.md
ADDED
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
# Development Guide - xAuthJWSK
|
|
2
|
+
|
|
3
|
+
Quick reference for developing with xAuthJWSK locally using test tokens.
|
|
4
|
+
|
|
5
|
+
## 🚀 Quick Start (30 seconds)
|
|
6
|
+
|
|
7
|
+
### 1. Start the demo server (uses local JWKS by default)
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
cd server
|
|
11
|
+
node app.js
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
You'll see:
|
|
15
|
+
```
|
|
16
|
+
🚀 xAuthJWSK Demo Server Started!
|
|
17
|
+
|
|
18
|
+
Mode: 🏠 LOCAL (Development)
|
|
19
|
+
Using example-jwks.json - perfect for testing!
|
|
20
|
+
Generate test tokens: node server/generate-demo-token.js [path] [userId] [role]
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### 2. Generate a test token (in another terminal)
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
cd server
|
|
27
|
+
node generate-demo-token.js admin user-123 admin
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Output:
|
|
31
|
+
```
|
|
32
|
+
🎫 Generated Test Token:
|
|
33
|
+
|
|
34
|
+
eyJhbGciOiJSUzI1NiIsImtpZCI6ImRlbW8ta2V5LWFkbWluIiwidHlwIjoiSldUIn0...
|
|
35
|
+
|
|
36
|
+
📋 Token Details:
|
|
37
|
+
Path: admin
|
|
38
|
+
User ID: user-123
|
|
39
|
+
Role: admin
|
|
40
|
+
Expires: 1 hour
|
|
41
|
+
|
|
42
|
+
🔗 Usage Examples:
|
|
43
|
+
|
|
44
|
+
# cURL:
|
|
45
|
+
curl -H "Authorization: Bearer eyJhbGciOiJSUzI1NiI..." \
|
|
46
|
+
http://localhost:3000/admin/dashboard
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### 3. Test the protected endpoint
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
# Replace TOKEN with actual token from step 2
|
|
53
|
+
TOKEN="eyJhbGciOiJSUzI1NiI..."
|
|
54
|
+
|
|
55
|
+
curl -H "Authorization: Bearer $TOKEN" \
|
|
56
|
+
http://localhost:3000/admin/dashboard
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Response:
|
|
60
|
+
```json
|
|
61
|
+
{
|
|
62
|
+
"message": "Admin Dashboard",
|
|
63
|
+
"userId": "user-123",
|
|
64
|
+
"user": {
|
|
65
|
+
"sub": "user-123",
|
|
66
|
+
"userId": "user-123",
|
|
67
|
+
"name": "Test User (admin)",
|
|
68
|
+
"email": "user-123@example.com",
|
|
69
|
+
"roles": ["admin"],
|
|
70
|
+
"iat": 1702000000,
|
|
71
|
+
"exp": 1702003600
|
|
72
|
+
},
|
|
73
|
+
"authenticatedVia": "admin"
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## 📝 Generate Test Tokens
|
|
78
|
+
|
|
79
|
+
### Different paths and roles
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
# Admin with admin role
|
|
83
|
+
node server/generate-demo-token.js admin user-123 admin
|
|
84
|
+
|
|
85
|
+
# Portal user with user role
|
|
86
|
+
node server/generate-demo-token.js portal user-456 user
|
|
87
|
+
|
|
88
|
+
# Partner with default user role
|
|
89
|
+
node server/generate-demo-token.js partner partner-789
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Decode a token (inspect claims)
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
# Extract the payload (middle part) and decode
|
|
96
|
+
TOKEN="eyJhbGciOiJSUzI1NiI...eyJzdWIiOiJ1c2VyLTEyMyI...abc123"
|
|
97
|
+
|
|
98
|
+
# Extract payload and decode
|
|
99
|
+
node -e "console.log(JSON.stringify(JSON.parse(Buffer.from('$TOKEN'.split('.')[1], 'base64').toString()), null, 2))"
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## 🏗️ Architecture
|
|
103
|
+
|
|
104
|
+
### Local JWKS Mode (Development)
|
|
105
|
+
|
|
106
|
+
```
|
|
107
|
+
┌─────────────────────────────────────┐
|
|
108
|
+
│ server/app.js │
|
|
109
|
+
├─────────────────────────────────────┤
|
|
110
|
+
│ USE_LOCAL_JWKS=true (default) │
|
|
111
|
+
│ Reads: example-jwks.json │
|
|
112
|
+
└────────────┬────────────────────────┘
|
|
113
|
+
│
|
|
114
|
+
↓
|
|
115
|
+
┌─────────────────────────────────────┐
|
|
116
|
+
│ xAuthJWSK Plugin │
|
|
117
|
+
├─────────────────────────────────────┤
|
|
118
|
+
│ paths: { │
|
|
119
|
+
│ admin: { │
|
|
120
|
+
│ jwksData: exampleJwks ← LOCAL │
|
|
121
|
+
│ } │
|
|
122
|
+
│ } │
|
|
123
|
+
└────────────┬────────────────────────┘
|
|
124
|
+
│
|
|
125
|
+
↓
|
|
126
|
+
┌─────────────────────────────────────┐
|
|
127
|
+
│ Test Token │
|
|
128
|
+
├─────────────────────────────────────┤
|
|
129
|
+
│ Signed with: private key │
|
|
130
|
+
│ Verified against: jwksData │
|
|
131
|
+
│ No network calls needed! │
|
|
132
|
+
└─────────────────────────────────────┘
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Remote JWKS Mode (Production)
|
|
136
|
+
|
|
137
|
+
```
|
|
138
|
+
USE_LOCAL_JWKS=false
|
|
139
|
+
ADMIN_JWKS_URL=https://auth.example.com/.well-known/jwks.json
|
|
140
|
+
↓
|
|
141
|
+
xAuthJWSK fetches remote JWKS
|
|
142
|
+
↓
|
|
143
|
+
Validates tokens against remote keys
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## 🔧 Server Configuration
|
|
147
|
+
|
|
148
|
+
### Use Local JWKS (Default)
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
# Default - no env vars needed
|
|
152
|
+
node app.js
|
|
153
|
+
|
|
154
|
+
# Explicit
|
|
155
|
+
USE_LOCAL_JWKS=true node app.js
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Use Remote JWKS URLs
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
export ADMIN_JWKS_URL="https://auth.example.com/admin/.well-known/jwks.json"
|
|
162
|
+
export PORTAL_JWKS_URL="https://auth.example.com/portal/.well-known/jwks.json"
|
|
163
|
+
export PARTNER_JWKS_URL="https://auth.example.com/partner/.well-known/jwks.json"
|
|
164
|
+
|
|
165
|
+
node app.js
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## 🧪 Testing Workflows
|
|
169
|
+
|
|
170
|
+
### Test Missing Token
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
curl http://localhost:3000/admin/dashboard
|
|
174
|
+
# Response: 401 Access token required
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### Test Invalid Token
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
curl -H "Authorization: Bearer invalid-token" \
|
|
181
|
+
http://localhost:3000/admin/dashboard
|
|
182
|
+
# Response: 401 Invalid token
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### Test Excluded Paths (no token needed)
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
# These don't require tokens
|
|
189
|
+
curl http://localhost:3000/admin/health
|
|
190
|
+
curl http://localhost:3000/admin/status
|
|
191
|
+
|
|
192
|
+
# Response: 200 OK
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### Test Role-Based Access
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
# Generate user token (not admin)
|
|
199
|
+
TOKEN=$(node server/generate-demo-token.js admin user-456 user | grep "Bearer" -A 1 | head -1)
|
|
200
|
+
|
|
201
|
+
# Try admin-only endpoint
|
|
202
|
+
curl -H "Authorization: Bearer $TOKEN" \
|
|
203
|
+
http://localhost:3000/admin/settings
|
|
204
|
+
# Response: 403 Insufficient permissions (requireRole('admin') failed)
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### Test Token Expiration
|
|
208
|
+
|
|
209
|
+
Check expiration in generated tokens:
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
# Token has exp claim set to 1 hour from now
|
|
213
|
+
# After 1 hour, the same token will fail validation
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
## 🔐 Key Management
|
|
217
|
+
|
|
218
|
+
### Current Demo Key
|
|
219
|
+
|
|
220
|
+
The demo uses a single RSA key defined in `generate-demo-token.js`:
|
|
221
|
+
- Kid: `demo-key-admin`
|
|
222
|
+
- Algorithm: RS256
|
|
223
|
+
- Public key: stored in `example-jwks.json`
|
|
224
|
+
- Private key: used only to sign test tokens
|
|
225
|
+
|
|
226
|
+
### Generate Your Own Keys
|
|
227
|
+
|
|
228
|
+
For production or custom development:
|
|
229
|
+
|
|
230
|
+
```bash
|
|
231
|
+
node generate-keys.js
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
See [KEYS_GENERATION.md](./KEYS_GENERATION.md) for details.
|
|
235
|
+
|
|
236
|
+
## 🔍 Debugging
|
|
237
|
+
|
|
238
|
+
### View Cache Statistics
|
|
239
|
+
|
|
240
|
+
```bash
|
|
241
|
+
curl http://localhost:3000/admin/cache/stats
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
Response:
|
|
245
|
+
```json
|
|
246
|
+
{
|
|
247
|
+
"cacheStats": {
|
|
248
|
+
"admin": {
|
|
249
|
+
"size": 2,
|
|
250
|
+
"enabled": true,
|
|
251
|
+
"ttl": 300000
|
|
252
|
+
},
|
|
253
|
+
"portal": {
|
|
254
|
+
"size": 0,
|
|
255
|
+
"enabled": true,
|
|
256
|
+
"ttl": 300000
|
|
257
|
+
},
|
|
258
|
+
"partner": {
|
|
259
|
+
"size": 0,
|
|
260
|
+
"enabled": true,
|
|
261
|
+
"ttl": 600000
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
### View Validator Configuration
|
|
268
|
+
|
|
269
|
+
```bash
|
|
270
|
+
curl http://localhost:3000/admin/validators
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
### Decode Token (server-side)
|
|
274
|
+
|
|
275
|
+
```bash
|
|
276
|
+
TOKEN="eyJhbGciOiJSUzI1NiI..."
|
|
277
|
+
|
|
278
|
+
curl -X POST http://localhost:3000/debug/decode-token \
|
|
279
|
+
-H "Content-Type: application/json" \
|
|
280
|
+
-d "{\"token\": \"$TOKEN\"}"
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
### Extract Token from Header
|
|
284
|
+
|
|
285
|
+
```bash
|
|
286
|
+
curl -X POST http://localhost:3000/debug/extract-token \
|
|
287
|
+
-H "Authorization: Bearer $TOKEN"
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
## 📊 Endpoints for Development
|
|
291
|
+
|
|
292
|
+
### Public (no token needed)
|
|
293
|
+
|
|
294
|
+
| Method | Path | Purpose |
|
|
295
|
+
|--------|------|---------|
|
|
296
|
+
| GET | `/` | API info |
|
|
297
|
+
| GET | `/health` | Health check |
|
|
298
|
+
| POST | `/debug/decode-token` | Decode JWT payload |
|
|
299
|
+
| POST | `/debug/extract-token` | Extract Bearer token |
|
|
300
|
+
| GET | `/admin/validators` | View validator configs |
|
|
301
|
+
|
|
302
|
+
### Protected - Admin Path
|
|
303
|
+
|
|
304
|
+
| Method | Path | Notes |
|
|
305
|
+
|--------|------|-------|
|
|
306
|
+
| GET | `/admin/dashboard` | Requires token |
|
|
307
|
+
| GET | `/admin/health` | Excluded (no token needed) |
|
|
308
|
+
| GET | `/admin/status` | Excluded (no token needed) |
|
|
309
|
+
| GET | `/admin/users` | List users |
|
|
310
|
+
| GET | `/admin/settings` | Requires `admin` role |
|
|
311
|
+
| POST | `/admin/cache/clear` | Clear JWT cache |
|
|
312
|
+
| GET | `/admin/cache/stats` | View cache stats |
|
|
313
|
+
|
|
314
|
+
### Protected - Portal Path
|
|
315
|
+
|
|
316
|
+
| Method | Path | Notes |
|
|
317
|
+
|--------|------|-------|
|
|
318
|
+
| GET | `/portal/dashboard` | Requires token |
|
|
319
|
+
| GET | `/portal/profile` | User profile |
|
|
320
|
+
| GET | `/portal/public/docs` | Excluded (no token needed) |
|
|
321
|
+
| GET | `/portal/public/pricing` | Excluded (no token needed) |
|
|
322
|
+
|
|
323
|
+
### Protected - Partner Path
|
|
324
|
+
|
|
325
|
+
| Method | Path | Notes |
|
|
326
|
+
|--------|------|-------|
|
|
327
|
+
| GET | `/partner/api/data` | Partner data |
|
|
328
|
+
| GET | `/partner/api/webhooks` | Partner webhooks |
|
|
329
|
+
|
|
330
|
+
## 🐛 Troubleshooting
|
|
331
|
+
|
|
332
|
+
### "Invalid token" on all requests
|
|
333
|
+
|
|
334
|
+
1. Check token is valid:
|
|
335
|
+
```bash
|
|
336
|
+
node -e "console.log(JSON.stringify(JSON.parse(Buffer.from('TOKEN'.split('.')[1], 'base64').toString()), null, 2))" | jq .
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
2. Check token hasn't expired:
|
|
340
|
+
```bash
|
|
341
|
+
node -e "const token = 'TOKEN'; const exp = JSON.parse(Buffer.from(token.split('.')[1], 'base64').toString()).exp; console.log('Expires:', new Date(exp * 1000).toISOString(), 'Valid:', exp * 1000 > Date.now());"
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
3. Check kid matches:
|
|
345
|
+
```bash
|
|
346
|
+
# Token header kid
|
|
347
|
+
node -e "const token = 'TOKEN'; const header = JSON.parse(Buffer.from(token.split('.')[0], 'base64').toString()); console.log('Token kid:', header.kid);"
|
|
348
|
+
|
|
349
|
+
# JWKS kids
|
|
350
|
+
cat example-jwks.json | jq '.keys[].kid'
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
### "Local JWKS not working"
|
|
354
|
+
|
|
355
|
+
1. Check `example-jwks.json` exists:
|
|
356
|
+
```bash
|
|
357
|
+
ls -la server/example-jwks.json
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
2. Validate JWKS JSON:
|
|
361
|
+
```bash
|
|
362
|
+
cat server/example-jwks.json | jq .
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
3. Ensure server is using local mode:
|
|
366
|
+
```bash
|
|
367
|
+
# Should say "🏠 LOCAL (Development)"
|
|
368
|
+
node app.js | head -10
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
### Cache not working
|
|
372
|
+
|
|
373
|
+
Check cache is enabled:
|
|
374
|
+
```bash
|
|
375
|
+
curl http://localhost:3000/admin/cache/stats | jq .
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
If `enabled: false`, restart server or check `enablePayloadCache` config.
|
|
379
|
+
|
|
380
|
+
## 📚 Related Documentation
|
|
381
|
+
|
|
382
|
+
- [KEYS_GENERATION.md](./KEYS_GENERATION.md) - Generate your own keys
|
|
383
|
+
- [QUICK_START.md](./QUICK_START.md) - Production setup
|
|
384
|
+
- [CACHING.md](./CACHING.md) - Caching configuration
|
|
385
|
+
- [JOSE_UTILITIES.md](./JOSE_UTILITIES.md) - Token inspection utilities
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
# Jose Utilities Reference
|
|
2
|
+
|
|
3
|
+
xAuthJWSK re-exports useful jose functions for advanced JWT manipulation without requiring a separate jose import.
|
|
4
|
+
|
|
5
|
+
## Available Utilities
|
|
6
|
+
|
|
7
|
+
### decodeToken(token)
|
|
8
|
+
|
|
9
|
+
Decode JWT payload without verification. Useful for inspecting claims without validation overhead.
|
|
10
|
+
|
|
11
|
+
**⚠️ Warning**: This does NOT verify the signature. Use only for inspection, never trust the claims without verification.
|
|
12
|
+
|
|
13
|
+
```javascript
|
|
14
|
+
import { decodeToken } from '@xenterprises/fastify-xauth-jwks/utils';
|
|
15
|
+
|
|
16
|
+
const token = request.headers.authorization?.split(' ')[1];
|
|
17
|
+
const payload = decodeToken(token);
|
|
18
|
+
|
|
19
|
+
console.log(payload.sub); // User ID
|
|
20
|
+
console.log(payload.exp); // Expiration time
|
|
21
|
+
console.log(payload.roles); // User roles
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
**Use Cases:**
|
|
25
|
+
- Inspecting token expiration before full validation
|
|
26
|
+
- Checking user ID in headers before processing
|
|
27
|
+
- Debugging JWT content
|
|
28
|
+
- Conditional routing based on claims
|
|
29
|
+
|
|
30
|
+
**Example: Check Token Expiration**
|
|
31
|
+
|
|
32
|
+
```javascript
|
|
33
|
+
import { decodeToken } from '@xenterprises/fastify-xauth-jwks/utils';
|
|
34
|
+
|
|
35
|
+
fastify.addHook('onRequest', async (request, reply) => {
|
|
36
|
+
const token = extractToken(request);
|
|
37
|
+
if (!token) return;
|
|
38
|
+
|
|
39
|
+
const payload = decodeToken(token);
|
|
40
|
+
|
|
41
|
+
// Check if token is about to expire
|
|
42
|
+
if (payload.exp && Date.now() / 1000 > payload.exp - 60) {
|
|
43
|
+
// Token expires in less than 1 minute
|
|
44
|
+
request.tokenAboutToExpire = true;
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### decodeHeader(token)
|
|
50
|
+
|
|
51
|
+
Decode JWT header without verification. Useful for inspecting algorithm, key ID, and other header claims.
|
|
52
|
+
|
|
53
|
+
```javascript
|
|
54
|
+
import { decodeHeader } from '@xenterprises/fastify-xauth-jwks/utils';
|
|
55
|
+
|
|
56
|
+
const token = request.headers.authorization?.split(' ')[1];
|
|
57
|
+
const header = decodeHeader(token);
|
|
58
|
+
|
|
59
|
+
console.log(header.alg); // Algorithm (HS256, RS256, etc.)
|
|
60
|
+
console.log(header.kid); // Key ID
|
|
61
|
+
console.log(header.typ); // Type (JWT)
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
**Use Cases:**
|
|
65
|
+
- Finding which key signed the token (kid)
|
|
66
|
+
- Verifying algorithm matches expected value
|
|
67
|
+
- Debugging JWT issues
|
|
68
|
+
- Routing tokens to different validators
|
|
69
|
+
|
|
70
|
+
**Example: Check Key ID**
|
|
71
|
+
|
|
72
|
+
```javascript
|
|
73
|
+
import { decodeHeader, extractToken } from '@xenterprises/fastify-xauth-jwks/utils';
|
|
74
|
+
|
|
75
|
+
fastify.addHook('onRequest', async (request, reply) => {
|
|
76
|
+
const token = extractToken(request);
|
|
77
|
+
if (!token) return;
|
|
78
|
+
|
|
79
|
+
const header = decodeHeader(token);
|
|
80
|
+
|
|
81
|
+
// Route to different validator based on key ID
|
|
82
|
+
if (header.kid === 'old-key') {
|
|
83
|
+
request.oldKeyWarning = true;
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
**Example: Validate Algorithm**
|
|
89
|
+
|
|
90
|
+
```javascript
|
|
91
|
+
import { decodeHeader, extractToken } from '@xenterprises/fastify-xauth-jwks/utils';
|
|
92
|
+
|
|
93
|
+
fastify.addHook('onRequest', async (request, reply) => {
|
|
94
|
+
const token = extractToken(request);
|
|
95
|
+
if (!token) return;
|
|
96
|
+
|
|
97
|
+
const header = decodeHeader(token);
|
|
98
|
+
|
|
99
|
+
// Only accept RS256, reject HS256
|
|
100
|
+
if (header.alg === 'HS256') {
|
|
101
|
+
return reply.code(401).send({ error: 'Insecure algorithm' });
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## When to Use vs When NOT to Use
|
|
107
|
+
|
|
108
|
+
### ✅ DO use decodeToken/decodeHeader for:
|
|
109
|
+
|
|
110
|
+
- Pre-validation inspection of claims
|
|
111
|
+
- Extracting metadata from token headers
|
|
112
|
+
- Conditional routing decisions
|
|
113
|
+
- Token debugging and inspection
|
|
114
|
+
- Displaying token info to user (read-only)
|
|
115
|
+
|
|
116
|
+
### ❌ DON'T use decodeToken/decodeHeader for:
|
|
117
|
+
|
|
118
|
+
- Making authorization decisions (trust signature verification via xAuthJWSK)
|
|
119
|
+
- Verifying user identity without validation
|
|
120
|
+
- Accepting claims as fact without verification
|
|
121
|
+
- Security-critical decisions
|
|
122
|
+
|
|
123
|
+
## Complete Example
|
|
124
|
+
|
|
125
|
+
```javascript
|
|
126
|
+
import Fastify from 'fastify';
|
|
127
|
+
import xAuthJWSK from '@xenterprises/fastify-xauth-jwks';
|
|
128
|
+
import { decodeToken, decodeHeader, extractToken } from '@xenterprises/fastify-xauth-jwks/utils';
|
|
129
|
+
|
|
130
|
+
const fastify = Fastify();
|
|
131
|
+
|
|
132
|
+
await fastify.register(xAuthJWSK, {
|
|
133
|
+
paths: {
|
|
134
|
+
api: {
|
|
135
|
+
pathPattern: "/api",
|
|
136
|
+
jwksUrl: "https://your-auth.com/.well-known/jwks.json",
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// Pre-validation inspection
|
|
142
|
+
fastify.addHook('onRequest', async (request, reply) => {
|
|
143
|
+
const token = extractToken(request);
|
|
144
|
+
if (!token) return;
|
|
145
|
+
|
|
146
|
+
const header = decodeHeader(token);
|
|
147
|
+
|
|
148
|
+
// Log key ID for audit
|
|
149
|
+
request.keyId = header.kid;
|
|
150
|
+
|
|
151
|
+
const payload = decodeToken(token);
|
|
152
|
+
|
|
153
|
+
// Check expiration before full validation
|
|
154
|
+
const expiresIn = (payload.exp * 1000) - Date.now();
|
|
155
|
+
if (expiresIn < 60000) {
|
|
156
|
+
request.tokenWarnings = ['Token expires in less than 1 minute'];
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
// Protected routes use request.auth (after xAuthJWSK validation)
|
|
161
|
+
fastify.get('/api/profile', (request) => {
|
|
162
|
+
return {
|
|
163
|
+
userId: request.auth.userId, // Verified via signature
|
|
164
|
+
warnings: request.tokenWarnings, // From pre-validation
|
|
165
|
+
keyId: request.keyId // From pre-validation
|
|
166
|
+
};
|
|
167
|
+
});
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## Performance Considerations
|
|
171
|
+
|
|
172
|
+
- `decodeToken()` and `decodeHeader()` are **very fast** (no crypto operations)
|
|
173
|
+
- Use liberally for inspection and logging
|
|
174
|
+
- They complete in microseconds (vs milliseconds for signature verification)
|
|
175
|
+
- Safe to call in every request without performance impact
|
|
176
|
+
|
|
177
|
+
## Error Handling
|
|
178
|
+
|
|
179
|
+
Both functions throw if token is malformed:
|
|
180
|
+
|
|
181
|
+
```javascript
|
|
182
|
+
import { decodeToken } from '@xenterprises/fastify-xauth-jwks/utils';
|
|
183
|
+
|
|
184
|
+
try {
|
|
185
|
+
const payload = decodeToken(badToken);
|
|
186
|
+
} catch (error) {
|
|
187
|
+
// Invalid JWT format
|
|
188
|
+
console.error('Malformed token:', error.message);
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
## No Other Jose Exports
|
|
193
|
+
|
|
194
|
+
xAuthJWSK intentionally does NOT export:
|
|
195
|
+
- `SignJWT`, `CompactSign` - Not needed (tokens come from auth provider)
|
|
196
|
+
- `generateKeyPair`, `generateSecret` - Not needed (use auth provider)
|
|
197
|
+
- `importJWK`, `importPKCS8` - Not needed (JWKS are fetched remotely)
|
|
198
|
+
- Encryption functions - Out of scope for validation only
|
|
199
|
+
|
|
200
|
+
This keeps the module slim and focused. If you need advanced jose features, import jose directly:
|
|
201
|
+
|
|
202
|
+
```javascript
|
|
203
|
+
import * as jose from 'jose';
|
|
204
|
+
```
|