or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

advanced-security.mdauthorization-flows.mdclient-authentication.mdconfiguration.mdgrant-types.mdindex.mdpassport-integration.mdprotected-resources.mdtoken-management.md

advanced-security.mddocs/

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

```