Skip to content

Commit 1bd0c75

Browse files
Fix OAuth state parameter security vulnerability (#521)
* Fix OAuth state parameter security vulnerability Replace clientId in state parameter with secure random tokens to prevent potential security issues. Update callback handler to use stored clientId instead of reading from state parameter. Supports both static and dynamic client registration flows while maintaining backward compatibility. * Add changeset * Remove redundant clientId storage We already persist the clientId in sqlite and post hibernation, gets picked up from the reconnect params where the clientId is stored.
1 parent 1861528 commit 1bd0c75

File tree

3 files changed

+14
-8
lines changed

3 files changed

+14
-8
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"agents": patch
3+
---
4+
5+
Fix OAuth state parameter security vulnerability by replacing client_id with secure random tokens

packages/agents/src/mcp/client.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -201,13 +201,13 @@ export class MCPClientManager {
201201
);
202202
}
203203
const code = url.searchParams.get("code");
204-
const clientId = url.searchParams.get("state");
204+
const state = url.searchParams.get("state");
205205
const urlParams = urlMatch.split("/");
206206
const serverId = urlParams[urlParams.length - 1];
207207
if (!code) {
208208
throw new Error("Unauthorized: no code provided");
209209
}
210-
if (!clientId) {
210+
if (!state) {
211211
throw new Error("Unauthorized: no state provided");
212212
}
213213

@@ -228,6 +228,9 @@ export class MCPClientManager {
228228
);
229229
}
230230

231+
// Get clientId from auth provider (stored during redirectToAuthorization) or fallback to state for backward compatibility
232+
const clientId = conn.options.transport.authProvider.clientId || state;
233+
231234
// Set the OAuth credentials
232235
conn.options.transport.authProvider.clientId = clientId;
233236
conn.options.transport.authProvider.serverId = serverId;

packages/agents/src/mcp/do-oauth-client-provider.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import type {
55
OAuthClientMetadata,
66
OAuthTokens
77
} from "@modelcontextprotocol/sdk/shared/auth.js";
8+
import { nanoid } from "nanoid";
89

910
// A slight extension to the standard OAuthClientProvider interface because `redirectToAuthorization` doesn't give us the interface we need
1011
// This allows us to track authentication for a specific server and associated dynamic client registration
@@ -122,12 +123,9 @@ export class DurableObjectOAuthClientProvider implements AgentsOAuthProvider {
122123
* and require user interact to initiate the redirect flow
123124
*/
124125
async redirectToAuthorization(authUrl: URL): Promise<void> {
125-
// We want to track the client ID in state here because the typescript SSE client sometimes does
126-
// a dynamic client registration AFTER generating this redirect URL.
127-
const client_id = authUrl.searchParams.get("client_id");
128-
if (client_id) {
129-
authUrl.searchParams.append("state", client_id);
130-
}
126+
// Generate secure random token for state parameter
127+
const stateToken = nanoid();
128+
authUrl.searchParams.set("state", stateToken);
131129
this._authUrl_ = authUrl.toString();
132130
}
133131

0 commit comments

Comments
 (0)