Skip to content
Open
21 changes: 21 additions & 0 deletions examples/clients/typescript/auth-test-dpop-bearer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#!/usr/bin/env node

import { runDpopClient } from './helpers/dpopClientFlow';
import { runAsCli } from './helpers/cliRunner';

/**
* Broken DPoP client: builds correct per-request proofs but presents the
* DPoP-bound token with the `Bearer` scheme instead of `DPoP`. Isolates a
* failure of sep-1932-client-dpop-auth-scheme (RFC 9449 §7.1).
*/
export async function runClient(serverUrl: string): Promise<void> {
await runDpopClient(serverUrl, {
scheme: 'Bearer',
freshProofPerRequest: true,
sendTokenRequestProof: true,
handleAsNonce: true,
handleRsNonce: true
});
}

runAsCli(runClient, import.meta.url, 'auth-test-dpop-bearer <server-url>');
22 changes: 22 additions & 0 deletions examples/clients/typescript/auth-test-dpop-no-as-nonce.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#!/usr/bin/env node

import { runDpopClient } from './helpers/dpopClientFlow';
import { runAsCli } from './helpers/cliRunner';

/**
* Broken DPoP client: ignores the authorization server's `use_dpop_nonce`
* challenge at the token endpoint (RFC 9449 §8) — it does not retry the token
* request with the supplied nonce. Isolates a failure of
* sep-1932-client-as-nonce.
*/
export async function runClient(serverUrl: string): Promise<void> {
await runDpopClient(serverUrl, {
scheme: 'DPoP',
freshProofPerRequest: true,
sendTokenRequestProof: true,
handleAsNonce: false,
handleRsNonce: true
});
}

runAsCli(runClient, import.meta.url, 'auth-test-dpop-no-as-nonce <server-url>');
25 changes: 25 additions & 0 deletions examples/clients/typescript/auth-test-dpop-no-nonce.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#!/usr/bin/env node

import { runDpopClient } from './helpers/dpopClientFlow';
import { runAsCli } from './helpers/cliRunner';

/**
* Nonce-incapable but otherwise compliant DPoP client (SEP-1932 / RFC 9449):
* presents the DPoP-bound token with the `DPoP` Authorization scheme and a fresh
* proof on every request, but implements NO `use_dpop_nonce` handling. Against
* the nonce-less `auth/dpop` scenario (where neither server issues a challenge)
* it completes the flow successfully — proving that a client which does not
* support nonces still passes when the server does not require one (the common
* case, since server nonces are OPTIONAL in RFC 9449 §8/§9).
*/
export async function runClient(serverUrl: string): Promise<void> {
await runDpopClient(serverUrl, {
scheme: 'DPoP',
freshProofPerRequest: true,
sendTokenRequestProof: true,
handleAsNonce: false,
handleRsNonce: false
});
}

runAsCli(runClient, import.meta.url, 'auth-test-dpop-no-nonce <server-url>');
22 changes: 22 additions & 0 deletions examples/clients/typescript/auth-test-dpop-no-rs-nonce.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#!/usr/bin/env node

import { runDpopClient } from './helpers/dpopClientFlow';
import { runAsCli } from './helpers/cliRunner';

/**
* Broken DPoP client: obtains the token correctly (handles the AS nonce) but
* ignores the MCP server's `use_dpop_nonce` challenge at the resource
* (RFC 9449 §9) — it does not retry the request with the supplied nonce.
* Isolates a failure of sep-1932-client-rs-nonce.
*/
export async function runClient(serverUrl: string): Promise<void> {
await runDpopClient(serverUrl, {
scheme: 'DPoP',
freshProofPerRequest: true,
sendTokenRequestProof: true,
handleAsNonce: true,
handleRsNonce: false
});
}

runAsCli(runClient, import.meta.url, 'auth-test-dpop-no-rs-nonce <server-url>');
27 changes: 27 additions & 0 deletions examples/clients/typescript/auth-test-dpop-no-token-proof.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#!/usr/bin/env node

import { runDpopClient } from './helpers/dpopClientFlow';
import { runAsCli } from './helpers/cliRunner';

/**
* Broken DPoP client: never sends a DPoP proof at the token endpoint, so it
* never asks for a sender-constrained token — the AS falls back to an unbound
* Bearer token. Isolates a failure of sep-1932-client-token-request-proof
* (RFC 9449 §5). Because the resulting token is unbound, the resource-side
* binding check (sep-1932-client-fresh-proof) also fails as a consequence.
*/
export async function runClient(serverUrl: string): Promise<void> {
await runDpopClient(serverUrl, {
scheme: 'DPoP',
freshProofPerRequest: true,
sendTokenRequestProof: false,
handleAsNonce: true,
handleRsNonce: true
});
}

runAsCli(
runClient,
import.meta.url,
'auth-test-dpop-no-token-proof <server-url>'
);
21 changes: 21 additions & 0 deletions examples/clients/typescript/auth-test-dpop-replay.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#!/usr/bin/env node

import { runDpopClient } from './helpers/dpopClientFlow';
import { runAsCli } from './helpers/cliRunner';

/**
* Broken DPoP client: uses the DPoP scheme correctly but reuses a single proof
* (same `jti`) across requests instead of a fresh one each time. Isolates a
* failure of sep-1932-client-fresh-proof.
*/
export async function runClient(serverUrl: string): Promise<void> {
await runDpopClient(serverUrl, {
scheme: 'DPoP',
freshProofPerRequest: false,
sendTokenRequestProof: true,
handleAsNonce: true,
handleRsNonce: true
});
}

runAsCli(runClient, import.meta.url, 'auth-test-dpop-replay <server-url>');
20 changes: 20 additions & 0 deletions examples/clients/typescript/auth-test-dpop.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#!/usr/bin/env node

import { runDpopClient } from './helpers/dpopClientFlow';
import { runAsCli } from './helpers/cliRunner';

/**
* Well-behaved DPoP client (SEP-1932 / RFC 9449): presents the DPoP-bound token
* with the `DPoP` Authorization scheme and a fresh proof on every MCP request.
*/
export async function runClient(serverUrl: string): Promise<void> {
await runDpopClient(serverUrl, {
scheme: 'DPoP',
freshProofPerRequest: true,
sendTokenRequestProof: true,
handleAsNonce: true,
handleRsNonce: true
});
}

runAsCli(runClient, import.meta.url, 'auth-test-dpop <server-url>');
8 changes: 8 additions & 0 deletions examples/clients/typescript/everything-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import {
} from './helpers/withOAuthRetry.js';
import { ConformanceOAuthProvider } from './helpers/ConformanceOAuthProvider.js';
import { runClient as issValidationClient } from './auth-test-iss-validation.js';
import { runClient as dpopClient } from './auth-test-dpop.js';
import { logger } from './helpers/logger.js';

/**
Expand Down Expand Up @@ -854,6 +855,13 @@ registerScenario(
runEnterpriseManagedAuthorization
);

// ============================================================================
// DPoP client conformance (SEP-1932)
// ============================================================================

registerScenario('auth/dpop', dpopClient);
registerScenario('auth/dpop-nonce', dpopClient);

// ============================================================================
// MRTR client conformance (SEP-2322)
// ============================================================================
Expand Down
Loading