Skip to content

Commit cc9ea7f

Browse files
authored
make client side client_id generation configurable in the oauth router (#734)
1 parent 1177814 commit cc9ea7f

File tree

4 files changed

+36
-6
lines changed

4 files changed

+36
-6
lines changed

src/server/auth/clients.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,5 @@ export interface OAuthRegisteredClientsStore {
1616
*
1717
* If unimplemented, dynamic client registration is unsupported.
1818
*/
19-
registerClient?(client: OAuthClientInformationFull): OAuthClientInformationFull | Promise<OAuthClientInformationFull>;
19+
registerClient?(client: Omit<OAuthClientInformationFull, "client_id">): OAuthClientInformationFull | Promise<OAuthClientInformationFull>;
2020
}

src/server/auth/handlers/register.test.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,26 @@ describe('Client Registration Handler', () => {
218218
expect(response.body.client_secret_expires_at).toBe(0);
219219
});
220220

221+
it('sets no client_id when clientIdGeneration=false', async () => {
222+
// Create handler with no expiry
223+
const customApp = express();
224+
const options: ClientRegistrationHandlerOptions = {
225+
clientsStore: mockClientStoreWithRegistration,
226+
clientIdGeneration: false
227+
};
228+
229+
customApp.use('/register', clientRegistrationHandler(options));
230+
231+
const response = await supertest(customApp)
232+
.post('/register')
233+
.send({
234+
redirect_uris: ['https://example.com/callback']
235+
});
236+
237+
expect(response.status).toBe(201);
238+
expect(response.body.client_id).toBeUndefined();
239+
});
240+
221241
it('handles client with all metadata fields', async () => {
222242
const fullClientMetadata: OAuthClientMetadata = {
223243
redirect_uris: ['https://example.com/callback'],

src/server/auth/handlers/register.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,22 @@ export type ClientRegistrationHandlerOptions = {
3131
* Registration endpoints are particularly sensitive to abuse and should be rate limited.
3232
*/
3333
rateLimit?: Partial<RateLimitOptions> | false;
34+
35+
/**
36+
* Whether to generate a client ID before calling the client registration endpoint.
37+
*
38+
* If not set, defaults to true.
39+
*/
40+
clientIdGeneration?: boolean;
3441
};
3542

3643
const DEFAULT_CLIENT_SECRET_EXPIRY_SECONDS = 30 * 24 * 60 * 60; // 30 days
3744

3845
export function clientRegistrationHandler({
3946
clientsStore,
4047
clientSecretExpirySeconds = DEFAULT_CLIENT_SECRET_EXPIRY_SECONDS,
41-
rateLimit: rateLimitConfig
48+
rateLimit: rateLimitConfig,
49+
clientIdGeneration = true,
4250
}: ClientRegistrationHandlerOptions): RequestHandler {
4351
if (!clientsStore.registerClient) {
4452
throw new Error("Client registration store does not support registering clients");
@@ -78,7 +86,6 @@ export function clientRegistrationHandler({
7886
const isPublicClient = clientMetadata.token_endpoint_auth_method === 'none'
7987

8088
// Generate client credentials
81-
const clientId = crypto.randomUUID();
8289
const clientSecret = isPublicClient
8390
? undefined
8491
: crypto.randomBytes(32).toString('hex');
@@ -89,14 +96,17 @@ export function clientRegistrationHandler({
8996
const secretExpiryTime = clientsDoExpire ? clientIdIssuedAt + clientSecretExpirySeconds : 0
9097
const clientSecretExpiresAt = isPublicClient ? undefined : secretExpiryTime
9198

92-
let clientInfo: OAuthClientInformationFull = {
99+
let clientInfo: Omit<OAuthClientInformationFull, "client_id"> & { client_id?: string } = {
93100
...clientMetadata,
94-
client_id: clientId,
95101
client_secret: clientSecret,
96102
client_id_issued_at: clientIdIssuedAt,
97103
client_secret_expires_at: clientSecretExpiresAt,
98104
};
99105

106+
if (clientIdGeneration) {
107+
clientInfo.client_id = crypto.randomUUID();
108+
}
109+
100110
clientInfo = await clientsStore.registerClient!(clientInfo);
101111
res.status(201).json(clientInfo);
102112
} catch (error) {

src/server/auth/router.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ export function mcpAuthRouter(options: AuthRouterOptions): RequestHandler {
142142
new URL(oauthMetadata.registration_endpoint).pathname,
143143
clientRegistrationHandler({
144144
clientsStore: options.provider.clientsStore,
145-
...options,
145+
...options.clientRegistrationOptions,
146146
})
147147
);
148148
}

0 commit comments

Comments
 (0)