0
# Advanced Security Features
1
2
FAPI compliance, DPoP, JARM, JAR, PAR, response encryption, and other advanced security mechanisms for high-security OAuth 2.0 and OpenID Connect implementations.
3
4
## Capabilities
5
6
### DPoP (Demonstrating Proof-of-Possession)
7
8
Implement sender-constrained access tokens using DPoP for enhanced security.
9
10
```typescript { .api }
11
/**
12
* Generate DPoP key pair
13
* @param alg - JWS algorithm (default: 'ES256')
14
* @param options - Key generation options
15
* @returns Promise resolving to cryptographic key pair
16
*/
17
function randomDPoPKeyPair(
18
alg?: string,
19
options?: GenerateKeyPairOptions
20
): Promise<CryptoKeyPair>;
21
22
/**
23
* Create DPoP handle for sender-constrained tokens
24
* @param config - Configuration instance
25
* @param keyPair - DPoP key pair for signing proofs
26
* @param options - JWT modification options
27
* @returns DPoP handle for use with requests
28
*/
29
function getDPoPHandle(
30
config: Configuration,
31
keyPair: CryptoKeyPair,
32
options?: ModifyAssertionOptions
33
): DPoPHandle;
34
```
35
36
**Usage Examples:**
37
38
```typescript
39
import * as client from "openid-client";
40
41
// Generate DPoP key pair
42
const dpopKeyPair = await client.randomDPoPKeyPair("ES256");
43
44
// Create DPoP handle
45
const dpopHandle = client.getDPoPHandle(config, dpopKeyPair);
46
47
// Use DPoP with authorization code grant
48
const tokens = await client.authorizationCodeGrant(
49
config,
50
currentUrl,
51
{ pkceCodeVerifier: codeVerifier },
52
undefined,
53
{ DPoP: dpopHandle }
54
);
55
56
// Check if access token is sender-constrained
57
if (tokens.token_type === "dpop") {
58
console.log("Access token is sender-constrained with DPoP");
59
}
60
61
// Use DPoP handle with protected resource requests
62
const response = await client.fetchProtectedResource(
63
config,
64
tokens.access_token,
65
new URL("https://api.example.com/data"),
66
"GET",
67
undefined,
68
undefined,
69
{ DPoP: dpopHandle }
70
);
71
72
// DPoP works with all token operations
73
const newTokens = await client.refreshTokenGrant(
74
config,
75
tokens.refresh_token,
76
undefined,
77
{ DPoP: dpopHandle }
78
);
79
```
80
81
### JARM (JWT Authorization Response Mode)
82
83
Enable JWT-secured authorization responses for enhanced security.
84
85
```typescript { .api }
86
/**
87
* Enable JWT Authorization Response Mode
88
* @param config - Configuration instance to modify
89
*/
90
function useJwtResponseMode(config: Configuration): void;
91
```
92
93
**Usage Examples:**
94
95
```typescript
96
import * as client from "openid-client";
97
98
// Enable JARM during discovery
99
const config = await client.discovery(
100
new URL("https://example.com"),
101
"client-id",
102
"client-secret",
103
undefined,
104
{
105
execute: [client.useJwtResponseMode]
106
}
107
);
108
109
// Or enable on existing configuration
110
client.useJwtResponseMode(config);
111
112
// Authorization responses will now be JWT-signed
113
const authUrl = client.buildAuthorizationUrl(config, {
114
redirect_uri: "https://example.com/callback",
115
scope: "openid profile",
116
response_mode: "jwt", // This is set automatically
117
code_challenge: codeChallenge,
118
code_challenge_method: "S256"
119
});
120
121
// Handle JWT response (library validates signature automatically)
122
const tokens = await client.authorizationCodeGrant(
123
config,
124
currentUrl, // Contains JWT response
125
{ pkceCodeVerifier: codeVerifier }
126
);
127
```
128
129
### OpenID Connect Hybrid Flow
130
131
Enable `code id_token` response type for hybrid flow with ID Token validation.
132
133
```typescript { .api }
134
/**
135
* Enable OpenID Connect hybrid flow (code id_token response type)
136
* @param config - Configuration instance to modify
137
*/
138
function useCodeIdTokenResponseType(config: Configuration): void;
139
```
140
141
**Usage Examples:**
142
143
```typescript
144
import * as client from "openid-client";
145
146
// Enable hybrid flow
147
const config = await client.discovery(
148
new URL("https://example.com"),
149
"client-id",
150
"client-secret",
151
undefined,
152
{
153
execute: [client.useCodeIdTokenResponseType]
154
}
155
);
156
157
// Build authorization URL (response_type=code id_token is set automatically)
158
const authUrl = client.buildAuthorizationUrl(config, {
159
redirect_uri: "https://example.com/callback",
160
scope: "openid profile email",
161
nonce: client.randomNonce(),
162
state: client.randomState(),
163
code_challenge: codeChallenge,
164
code_challenge_method: "S256"
165
});
166
167
// Handle hybrid flow response
168
const tokens = await client.authorizationCodeGrant(
169
config,
170
currentUrl, // Contains both code and id_token
171
{
172
pkceCodeVerifier: codeVerifier,
173
expectedNonce: expectedNonce,
174
expectedState: expectedState
175
}
176
);
177
178
// Access both authorization response ID Token and token endpoint tokens
179
console.log("Auth response ID Token:", tokens.claims());
180
console.log("Token endpoint access token:", tokens.access_token);
181
```
182
183
### OpenID Connect Implicit Flow
184
185
Enable ID Token-only implicit flow for specific use cases.
186
187
```typescript { .api }
188
/**
189
* Enable OpenID Connect implicit flow (id_token response type)
190
* @param config - Configuration instance to modify
191
*/
192
function useIdTokenResponseType(config: Configuration): void;
193
```
194
195
**Usage Examples:**
196
197
```typescript
198
import * as client from "openid-client";
199
200
// Enable implicit flow (typically for public clients)
201
const config = await client.discovery(
202
new URL("https://example.com"),
203
"public-client-id",
204
undefined,
205
client.None(), // Public client authentication
206
{
207
execute: [client.useIdTokenResponseType]
208
}
209
);
210
211
// Build authorization URL for implicit flow
212
const authUrl = client.buildAuthorizationUrl(config, {
213
redirect_uri: "https://spa.example.com/callback",
214
scope: "openid profile email",
215
nonce: client.randomNonce(),
216
state: client.randomState()
217
// response_type=id_token is set automatically
218
});
219
220
// Handle implicit flow response (use implicitAuthentication, not authorizationCodeGrant)
221
const idTokenClaims = await client.implicitAuthentication(
222
config,
223
new URL(location.href), // Browser location with hash fragment
224
expectedNonce,
225
{
226
expectedState: expectedState,
227
maxAge: 3600
228
}
229
);
230
231
console.log("User authenticated:", idTokenClaims.sub);
232
console.log("Email:", idTokenClaims.email);
233
```
234
235
### FAPI 1.0 Advanced Security
236
237
Enable FAPI 1.0 Advanced profile with detached signature response checks.
238
239
```typescript { .api }
240
/**
241
* Enable FAPI 1.0 Advanced detached signature response checks
242
* @param config - Configuration instance to modify
243
*/
244
function enableDetachedSignatureResponseChecks(config: Configuration): void;
245
```
246
247
**Usage Examples:**
248
249
```typescript
250
import * as client from "openid-client";
251
252
// Enable FAPI 1.0 Advanced profile
253
const config = await client.discovery(
254
new URL("https://fapi-server.example.com"),
255
"client-id",
256
"client-secret",
257
undefined,
258
{
259
execute: [
260
client.useCodeIdTokenResponseType, // Required for FAPI Advanced
261
client.enableDetachedSignatureResponseChecks // Enable FAPI validation
262
]
263
}
264
);
265
266
// FAPI requires specific security measures
267
const authUrl = client.buildAuthorizationUrl(config, {
268
redirect_uri: "https://example.com/callback",
269
scope: "openid accounts",
270
nonce: client.randomNonce(),
271
state: client.randomState(),
272
code_challenge: codeChallenge,
273
code_challenge_method: "S256" // PKCE required for FAPI
274
});
275
276
// Handle FAPI response with enhanced validation
277
const tokens = await client.authorizationCodeGrant(
278
config,
279
currentUrl,
280
{
281
pkceCodeVerifier: codeVerifier,
282
expectedNonce: expectedNonce,
283
expectedState: expectedState
284
}
285
);
286
```
287
288
### Response Signature Validation
289
290
Enable JWS signature validation for enhanced security.
291
292
```typescript { .api }
293
/**
294
* Enable non-repudiation checks (JWS signature validation)
295
* @param config - Configuration instance to modify
296
*/
297
function enableNonRepudiationChecks(config: Configuration): void;
298
```
299
300
**Usage Examples:**
301
302
```typescript
303
import * as client from "openid-client";
304
305
// Enable signature validation
306
const config = await client.discovery(
307
new URL("https://example.com"),
308
"client-id",
309
"client-secret",
310
undefined,
311
{
312
execute: [client.enableNonRepudiationChecks]
313
}
314
);
315
316
// All JWT responses (UserInfo, Introspection, etc.) will be signature-validated
317
const userInfo = await client.fetchUserInfo(
318
config,
319
accessToken,
320
expectedSubject
321
);
322
323
// JWT introspection responses are validated
324
const introspection = await client.tokenIntrospection(config, token);
325
```
326
327
### Response Encryption
328
329
Enable JWE decryption for encrypted responses.
330
331
```typescript { .api }
332
/**
333
* Enable decryption of encrypted responses
334
* @param config - Configuration instance to modify
335
* @param contentEncryptionAlgorithms - Allowed content encryption algorithms
336
* @param keys - Decryption keys
337
*/
338
function enableDecryptingResponses(
339
config: Configuration,
340
contentEncryptionAlgorithms?: string[],
341
...keys: Array<CryptoKey | DecryptionKey>
342
): void;
343
```
344
345
**Usage Examples:**
346
347
```typescript
348
import * as client from "openid-client";
349
350
// Generate or import decryption key
351
const keyPair = await crypto.subtle.generateKey(
352
{ name: "ECDH", namedCurve: "P-256" },
353
true,
354
["deriveKey"]
355
);
356
357
// Enable response decryption
358
client.enableDecryptingResponses(
359
config,
360
["A256GCM", "A128CBC-HS256"], // Allowed content encryption algorithms
361
keyPair.privateKey
362
);
363
364
// Or with DecryptionKey interface for more control
365
const decryptionKey: client.DecryptionKey = {
366
key: keyPair.privateKey,
367
alg: "ECDH-ES+A256KW", // Key management algorithm
368
kid: "my-decryption-key-id"
369
};
370
371
client.enableDecryptingResponses(
372
config,
373
["A256GCM"],
374
decryptionKey
375
);
376
377
// Encrypted responses will be automatically decrypted
378
const userInfo = await client.fetchUserInfo(config, accessToken, expectedSubject);
379
const introspection = await client.tokenIntrospection(config, token);
380
```
381
382
### JWKS Caching for Stateless Environments
383
384
Manage JWKS cache for cloud functions and stateless environments.
385
386
```typescript { .api }
387
/**
388
* Export JWKS cache for external storage
389
* @param config - Configuration instance
390
* @returns Exported JWKS cache or undefined
391
*/
392
function getJwksCache(config: Configuration): ExportedJWKSCache | undefined;
393
394
/**
395
* Import JWKS cache from external storage
396
* @param config - Configuration instance
397
* @param jwksCache - Previously exported JWKS cache
398
*/
399
function setJwksCache(config: Configuration, jwksCache: ExportedJWKSCache): void;
400
```
401
402
**Usage Examples:**
403
404
```typescript
405
import * as client from "openid-client";
406
407
// In a serverless function
408
export async function handler(event: any) {
409
const config = await client.discovery(
410
new URL("https://example.com"),
411
"client-id",
412
"client-secret"
413
);
414
415
// Load JWKS cache from external storage (Redis, DynamoDB, etc.)
416
const savedCache = await loadJwksCacheFromStorage();
417
if (savedCache) {
418
client.setJwksCache(config, savedCache);
419
}
420
421
// Perform operations that may use JWKS
422
const userInfo = await client.fetchUserInfo(config, accessToken, expectedSubject);
423
424
// Save updated JWKS cache
425
const currentCache = client.getJwksCache(config);
426
if (currentCache) {
427
await saveJwksCacheToStorage(currentCache);
428
}
429
430
return { statusCode: 200, body: JSON.stringify(userInfo) };
431
}
432
433
// Cache storage implementation example
434
async function loadJwksCacheFromStorage(): Promise<client.ExportedJWKSCache | null> {
435
try {
436
const cached = await redis.get("jwks-cache");
437
return cached ? JSON.parse(cached) : null;
438
} catch {
439
return null;
440
}
441
}
442
443
async function saveJwksCacheToStorage(cache: client.ExportedJWKSCache): Promise<void> {
444
try {
445
await redis.setex("jwks-cache", 3600, JSON.stringify(cache)); // 1 hour TTL
446
} catch (error) {
447
console.warn("Failed to save JWKS cache:", error);
448
}
449
}
450
```
451
452
### Custom Security Configuration
453
454
Override default security settings for development or specific requirements.
455
456
```typescript { .api }
457
/**
458
* Allow insecure HTTP requests (development only)
459
* @param config - Configuration instance to modify
460
*/
461
function allowInsecureRequests(config: Configuration): void;
462
```
463
464
**Usage Examples:**
465
466
```typescript
467
import * as client from "openid-client";
468
469
// For development/testing only - allow HTTP
470
const config = await client.discovery(
471
new URL("http://localhost:8080"), // HTTP issuer
472
"dev-client-id",
473
"dev-client-secret",
474
undefined,
475
{
476
execute: [client.allowInsecureRequests]
477
}
478
);
479
480
// Or enable on existing configuration
481
client.allowInsecureRequests(config);
482
```
483
484
## Error Types
485
486
Re-exported error classes from oauth4webapi for comprehensive error handling:
487
488
```typescript { .api }
489
/**
490
* Authorization endpoint error response
491
*/
492
class AuthorizationResponseError extends Error {
493
error: string;
494
error_description?: string;
495
error_uri?: string;
496
state?: string;
497
}
498
499
/**
500
* HTTP response body parsing error
501
*/
502
class ResponseBodyError extends Error {
503
response: Response;
504
}
505
506
/**
507
* WWW-Authenticate header parsing error
508
*/
509
class WWWAuthenticateChallengeError extends Error {
510
challenges: WWWAuthenticateChallenge[];
511
}
512
```
513
514
## Advanced Types
515
516
```typescript { .api }
517
interface DecryptionKey {
518
/** Decryption private key */
519
key: CryptoKey;
520
/** JWE Key Management Algorithm identifier */
521
alg?: string;
522
/** Key ID */
523
kid?: string;
524
}
525
526
interface DPoPHandle {
527
// DPoP handle implementation (opaque to consumers)
528
}
529
530
interface GenerateKeyPairOptions {
531
/** Whether the key should be extractable */
532
extractable?: boolean;
533
}
534
535
interface ExportedJWKSCache {
536
/** Cached JWKS data */
537
jwks: any;
538
/** Cache timestamp */
539
uat: number;
540
}
541
542
interface WWWAuthenticateChallenge {
543
scheme: string;
544
parameters: WWWAuthenticateChallengeParameters;
545
}
546
547
interface WWWAuthenticateChallengeParameters {
548
[parameter: string]: string;
549
}
550
```
551
552
## Security Symbols
553
554
```typescript { .api }
555
/**
556
* Symbol for JWT modification in assertions
557
*/
558
declare const modifyAssertion: unique symbol;
559
560
/**
561
* Symbol for clock skew adjustment
562
*/
563
declare const clockSkew: unique symbol;
564
565
/**
566
* Symbol for clock tolerance configuration
567
*/
568
declare const clockTolerance: unique symbol;
569
```
570
571
**Symbol Usage Examples:**
572
573
```typescript
574
import * as client from "openid-client";
575
576
// Clock management for JWT validation
577
const clientMetadata: client.ClientMetadata = {
578
client_id: "client-id",
579
client_secret: "client-secret",
580
[client.clockSkew]: 30, // Local clock is 30 seconds behind server
581
[client.clockTolerance]: 60 // Allow 60 seconds tolerance for JWT validation
582
};
583
584
const config = new client.Configuration(
585
serverMetadata,
586
"client-id",
587
clientMetadata
588
);
589
590
// JWT assertion modification
591
const privateKeyJwtAuth = client.PrivateKeyJwt(privateKey, {
592
[client.modifyAssertion]: (header, payload) => {
593
// Modify JWT before signing
594
header.kid = "my-key-id";
595
payload.jti = crypto.randomUUID();
596
payload.iat = Math.floor(Date.now() / 1000) - 30; // 30 seconds ago
597
}
598
});
599
600
// Security override symbols (use with caution)
601
const tokens = await client.authorizationCodeGrant(
602
config,
603
currentUrl,
604
{
605
expectedState: client.skipStateCheck, // Skip state validation (not recommended)
606
expectedNonce: expectedNonce
607
}
608
);
609
610
const userInfo = await client.fetchUserInfo(
611
config,
612
tokens.access_token,
613
client.skipSubjectCheck // Skip subject validation (not recommended)
614
);
615
```