Skip to content

Commit f3e904d

Browse files
committed
prefer the token_endpoint_auth_method from DCR registration
1 parent 7d29cee commit f3e904d

File tree

5 files changed

+35
-12
lines changed

5 files changed

+35
-12
lines changed

src/client/auth.test.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1635,6 +1635,7 @@ describe("OAuth Authorization", () => {
16351635
(mockProvider.clientInformation as jest.Mock).mockResolvedValue({
16361636
client_id: "test-client",
16371637
client_secret: "test-secret",
1638+
redirect_uris: ["http://localhost:3000/callback"],
16381639
});
16391640
(mockProvider.tokens as jest.Mock).mockResolvedValue(undefined);
16401641
(mockProvider.saveCodeVerifier as jest.Mock).mockResolvedValue(undefined);
@@ -1705,6 +1706,7 @@ describe("OAuth Authorization", () => {
17051706
(mockProvider.clientInformation as jest.Mock).mockResolvedValue({
17061707
client_id: "test-client",
17071708
client_secret: "test-secret",
1709+
redirect_uris: ["http://localhost:3000/callback"],
17081710
});
17091711
(mockProvider.codeVerifier as jest.Mock).mockResolvedValue("test-verifier");
17101712
(mockProvider.saveTokens as jest.Mock).mockResolvedValue(undefined);
@@ -1773,6 +1775,7 @@ describe("OAuth Authorization", () => {
17731775
(mockProvider.clientInformation as jest.Mock).mockResolvedValue({
17741776
client_id: "test-client",
17751777
client_secret: "test-secret",
1778+
redirect_uris: ["http://localhost:3000/callback"],
17761779
});
17771780
(mockProvider.tokens as jest.Mock).mockResolvedValue({
17781781
access_token: "old-access",
@@ -1841,6 +1844,7 @@ describe("OAuth Authorization", () => {
18411844
(providerWithCustomValidation.clientInformation as jest.Mock).mockResolvedValue({
18421845
client_id: "test-client",
18431846
client_secret: "test-secret",
1847+
redirect_uris: ["http://localhost:3000/callback"],
18441848
});
18451849
(providerWithCustomValidation.tokens as jest.Mock).mockResolvedValue(undefined);
18461850
(providerWithCustomValidation.saveCodeVerifier as jest.Mock).mockResolvedValue(undefined);
@@ -1896,6 +1900,7 @@ describe("OAuth Authorization", () => {
18961900
(mockProvider.clientInformation as jest.Mock).mockResolvedValue({
18971901
client_id: "test-client",
18981902
client_secret: "test-secret",
1903+
redirect_uris: ["http://localhost:3000/callback"],
18991904
});
19001905
(mockProvider.tokens as jest.Mock).mockResolvedValue(undefined);
19011906
(mockProvider.saveCodeVerifier as jest.Mock).mockResolvedValue(undefined);
@@ -1954,6 +1959,7 @@ describe("OAuth Authorization", () => {
19541959
(mockProvider.clientInformation as jest.Mock).mockResolvedValue({
19551960
client_id: "test-client",
19561961
client_secret: "test-secret",
1962+
redirect_uris: ["http://localhost:3000/callback"],
19571963
});
19581964
(mockProvider.tokens as jest.Mock).mockResolvedValue(undefined);
19591965
(mockProvider.saveCodeVerifier as jest.Mock).mockResolvedValue(undefined);
@@ -2021,6 +2027,7 @@ describe("OAuth Authorization", () => {
20212027
(mockProvider.clientInformation as jest.Mock).mockResolvedValue({
20222028
client_id: "test-client",
20232029
client_secret: "test-secret",
2030+
redirect_uris: ["http://localhost:3000/callback"],
20242031
});
20252032
(mockProvider.codeVerifier as jest.Mock).mockResolvedValue("test-verifier");
20262033
(mockProvider.saveTokens as jest.Mock).mockResolvedValue(undefined);
@@ -2086,6 +2093,7 @@ describe("OAuth Authorization", () => {
20862093
(mockProvider.clientInformation as jest.Mock).mockResolvedValue({
20872094
client_id: "test-client",
20882095
client_secret: "test-secret",
2096+
redirect_uris: ["http://localhost:3000/callback"],
20892097
});
20902098
(mockProvider.tokens as jest.Mock).mockResolvedValue({
20912099
access_token: "old-access",
@@ -2149,6 +2157,7 @@ describe("OAuth Authorization", () => {
21492157
(mockProvider.clientInformation as jest.Mock).mockResolvedValue({
21502158
client_id: "test-client",
21512159
client_secret: "test-secret",
2160+
redirect_uris: ["http://localhost:3000/callback"],
21522161
});
21532162
(mockProvider.tokens as jest.Mock).mockResolvedValue(undefined);
21542163
(mockProvider.saveCodeVerifier as jest.Mock).mockResolvedValue(undefined);
@@ -2209,6 +2218,7 @@ describe("OAuth Authorization", () => {
22092218
clientInformation: jest.fn().mockResolvedValue({
22102219
client_id: "client123",
22112220
client_secret: "secret123",
2221+
redirect_uris: ["http://localhost:3000/callback"],
22122222
}),
22132223
tokens: jest.fn().mockResolvedValue(undefined),
22142224
saveTokens: jest.fn(),

src/client/auth.ts

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import pkceChallenge from "pkce-challenge";
22
import { LATEST_PROTOCOL_VERSION } from "../types.js";
33
import {
44
OAuthClientMetadata,
5-
OAuthClientInformation,
65
OAuthTokens,
76
OAuthMetadata,
87
OAuthClientInformationFull,
@@ -51,7 +50,7 @@ export interface OAuthClientProvider {
5150
* server, or returns `undefined` if the client is not registered with the
5251
* server.
5352
*/
54-
clientInformation(): OAuthClientInformation | undefined | Promise<OAuthClientInformation | undefined>;
53+
clientInformation(): OAuthClientInformationFull | undefined | Promise<OAuthClientInformationFull | undefined>;
5554

5655
/**
5756
* If implemented, this permits the OAuth client to dynamically register with
@@ -139,6 +138,10 @@ export class UnauthorizedError extends Error {
139138

140139
type ClientAuthMethod = 'client_secret_basic' | 'client_secret_post' | 'none';
141140

141+
function isClientAuthMethod(method: string): method is ClientAuthMethod {
142+
return ["client_secret_basic", "client_secret_post", "none"].includes(method);
143+
}
144+
142145
/**
143146
* Determines the best client authentication method to use based on server support and client configuration.
144147
*
@@ -152,7 +155,7 @@ type ClientAuthMethod = 'client_secret_basic' | 'client_secret_post' | 'none';
152155
* @returns The selected authentication method
153156
*/
154157
function selectClientAuthMethod(
155-
clientInformation: OAuthClientInformation,
158+
clientInformation: OAuthClientInformationFull,
156159
supportedMethods: string[]
157160
): ClientAuthMethod {
158161
const hasClientSecret = clientInformation.client_secret !== undefined;
@@ -162,6 +165,15 @@ function selectClientAuthMethod(
162165
return hasClientSecret ? "client_secret_post" : "none";
163166
}
164167

168+
// Prefer the method returned by the server during client registration if valid and supported
169+
if (
170+
clientInformation.token_endpoint_auth_method &&
171+
isClientAuthMethod(clientInformation.token_endpoint_auth_method) &&
172+
supportedMethods.includes(clientInformation.token_endpoint_auth_method)
173+
) {
174+
return clientInformation.token_endpoint_auth_method;
175+
}
176+
165177
// Try methods in priority order (most secure first)
166178
if (hasClientSecret && supportedMethods.includes("client_secret_basic")) {
167179
return "client_secret_basic";
@@ -195,7 +207,7 @@ function selectClientAuthMethod(
195207
*/
196208
function applyClientAuthentication(
197209
method: ClientAuthMethod,
198-
clientInformation: OAuthClientInformation,
210+
clientInformation: OAuthClientInformationFull,
199211
headers: Headers,
200212
params: URLSearchParams
201213
): void {
@@ -809,7 +821,7 @@ export async function startAuthorization(
809821
resource,
810822
}: {
811823
metadata?: AuthorizationServerMetadata;
812-
clientInformation: OAuthClientInformation;
824+
clientInformation: OAuthClientInformationFull;
813825
redirectUrl: string | URL;
814826
scope?: string;
815827
state?: string;
@@ -902,7 +914,7 @@ export async function exchangeAuthorization(
902914
fetchFn,
903915
}: {
904916
metadata?: AuthorizationServerMetadata;
905-
clientInformation: OAuthClientInformation;
917+
clientInformation: OAuthClientInformationFull;
906918
authorizationCode: string;
907919
codeVerifier: string;
908920
redirectUri: string | URL;
@@ -988,7 +1000,7 @@ export async function refreshAuthorization(
9881000
fetchFn,
9891001
}: {
9901002
metadata?: AuthorizationServerMetadata;
991-
clientInformation: OAuthClientInformation;
1003+
clientInformation: OAuthClientInformationFull;
9921004
refreshToken: string;
9931005
resource?: URL;
9941006
addClientAuthentication?: OAuthClientProvider["addClientAuthentication"];

src/client/sse.test.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -363,7 +363,7 @@ describe("SSEClientTransport", () => {
363363
mockAuthProvider = {
364364
get redirectUrl() { return "http://localhost/callback"; },
365365
get clientMetadata() { return { redirect_uris: ["http://localhost/callback"] }; },
366-
clientInformation: jest.fn(() => ({ client_id: "test-client-id", client_secret: "test-client-secret" })),
366+
clientInformation: jest.fn(() => ({ client_id: "test-client-id", client_secret: "test-client-secret", redirect_uris: ["http://localhost/callback"] })),
367367
tokens: jest.fn(),
368368
saveTokens: jest.fn(),
369369
redirectToAuthorization: jest.fn(),
@@ -1140,7 +1140,8 @@ describe("SSEClientTransport", () => {
11401140

11411141
const clientInfo = config.clientRegistered ? {
11421142
client_id: "test-client-id",
1143-
client_secret: "test-client-secret"
1143+
client_secret: "test-client-secret",
1144+
redirect_uris: ["http://localhost/callback"],
11441145
} : undefined;
11451146

11461147
return {

src/client/streamableHttp.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ describe("StreamableHTTPClientTransport", () => {
1212
mockAuthProvider = {
1313
get redirectUrl() { return "http://localhost/callback"; },
1414
get clientMetadata() { return { redirect_uris: ["http://localhost/callback"] }; },
15-
clientInformation: jest.fn(() => ({ client_id: "test-client-id", client_secret: "test-client-secret" })),
15+
clientInformation: jest.fn(() => ({ client_id: "test-client-id", client_secret: "test-client-secret", redirect_uris: ["http://localhost/callback"] })),
1616
tokens: jest.fn(),
1717
saveTokens: jest.fn(),
1818
redirectToAuthorization: jest.fn(),

src/examples/client/simpleOAuthClient.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { URL } from 'node:url';
66
import { exec } from 'node:child_process';
77
import { Client } from '../../client/index.js';
88
import { StreamableHTTPClientTransport } from '../../client/streamableHttp.js';
9-
import { OAuthClientInformation, OAuthClientInformationFull, OAuthClientMetadata, OAuthTokens } from '../../shared/auth.js';
9+
import { OAuthClientInformationFull, OAuthClientMetadata, OAuthTokens } from '../../shared/auth.js';
1010
import {
1111
CallToolRequest,
1212
ListToolsRequest,
@@ -49,7 +49,7 @@ class InMemoryOAuthClientProvider implements OAuthClientProvider {
4949
return this._clientMetadata;
5050
}
5151

52-
clientInformation(): OAuthClientInformation | undefined {
52+
clientInformation(): OAuthClientInformationFull | undefined {
5353
return this._clientInformation;
5454
}
5555

0 commit comments

Comments
 (0)