Skip to content

Commit 5e6095d

Browse files
authored
Merge pull request #698 from KKonstantinov/feature/inspector-oauth-client-provider-state
OAuth state parameter for InspectorOAuthClientProvider via reusable generateOAuthState helper
2 parents 113b612 + 60049a5 commit 5e6095d

File tree

4 files changed

+29
-8
lines changed

4 files changed

+29
-8
lines changed

client/src/lib/auth.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
OAuthMetadata,
99
} from "@modelcontextprotocol/sdk/shared/auth.js";
1010
import { SESSION_KEYS, getServerSpecificKey } from "./constants";
11+
import { generateOAuthState } from "@/utils/oauthUtils";
1112

1213
export const getClientInformationFromSessionStorage = async ({
1314
serverUrl,
@@ -86,6 +87,10 @@ export class InspectorOAuthClientProvider implements OAuthClientProvider {
8687
};
8788
}
8889

90+
state(): string | Promise<string> {
91+
return generateOAuthState();
92+
}
93+
8994
async clientInformation() {
9095
// Try to get the preregistered client information from session storage first
9196
const preregisteredClientInformation =

client/src/lib/oauth-state-machine.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
OAuthMetadataSchema,
1313
OAuthProtectedResourceMetadata,
1414
} from "@modelcontextprotocol/sdk/shared/auth.js";
15+
import { generateOAuthState } from "@/utils/oauthUtils";
1516

1617
export interface StateMachineContext {
1718
state: AuthDebuggerState;
@@ -113,21 +114,14 @@ export const oauthTransitions: Record<OAuthStep, StateTransition> = {
113114
scope = metadata.scopes_supported.join(" ");
114115
}
115116

116-
// Generate a random state
117-
const array = new Uint8Array(32);
118-
crypto.getRandomValues(array);
119-
const state = Array.from(array, (byte) =>
120-
byte.toString(16).padStart(2, "0"),
121-
).join("");
122-
123117
const { authorizationUrl, codeVerifier } = await startAuthorization(
124118
context.serverUrl,
125119
{
126120
metadata,
127121
clientInformation,
128122
redirectUrl: context.provider.redirectUrl,
129123
scope,
130-
state: state,
124+
state: generateOAuthState(),
131125
resource: context.state.resource ?? undefined,
132126
},
133127
);

client/src/utils/__tests__/oauthUtils.ts renamed to client/src/utils/__tests__/oauthUtils.test.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {
22
generateOAuthErrorDescription,
33
parseOAuthCallbackParams,
4+
generateOAuthState,
45
} from "@/utils/oauthUtils.ts";
56

67
describe("parseOAuthCallbackParams", () => {
@@ -75,4 +76,11 @@ describe("generateOAuthErrorDescription", () => {
7576
"Error: invalid_request.\nDetails: The request could not be completed as dialed.\nMore info: https://example.com/error-docs.",
7677
);
7778
});
79+
80+
describe("generateOAuthState", () => {
81+
it("Returns a string", () => {
82+
expect(generateOAuthState()).toBeDefined();
83+
expect(generateOAuthState()).toHaveLength(64);
84+
});
85+
});
7886
});

client/src/utils/oauthUtils.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,20 @@ export const parseOAuthCallbackParams = (location: string): CallbackParams => {
4848
};
4949
};
5050

51+
/**
52+
* Generate a random state for the OAuth 2.0 flow.
53+
*
54+
* @returns A random state for the OAuth 2.0 flow.
55+
*/
56+
export const generateOAuthState = () => {
57+
// Generate a random state
58+
const array = new Uint8Array(32);
59+
crypto.getRandomValues(array);
60+
return Array.from(array, (byte) => byte.toString(16).padStart(2, "0")).join(
61+
"",
62+
);
63+
};
64+
5165
export const generateOAuthErrorDescription = (
5266
params: Extract<CallbackParams, { successful: false }>,
5367
): string => {

0 commit comments

Comments
 (0)