Skip to content

Conversation

@DeanMauro
Copy link
Contributor

@DeanMauro DeanMauro commented Dec 9, 2025

Add Client Props Support

This PR adds support for storing application-specific props on OAuth clients.

Features

1. Client Props Support

Clients can now store application-specific props that are separate from the standard OAuth client registration fields. This allows applications to attach custom data to clients for their own use cases.

  • Added props field to ClientInfo<Props> interface (generic type parameter for type safety. Defaults to any for backward compatibility)
  • Props can be set when creating clients via createClient() or during dynamic client registration via clientRegistrationCallback
  • Props are stored with the client and can be retrieved using lookupClient() or listClients()
// Create a client with typed props
const client = await env.OAUTH_PROVIDER.createClient<{ appId: string; clientType: string }>({
  redirectUris: ['https://client.example.com/callback'],
  clientName: 'My App',
  tokenEndpointAuthMethod: 'client_secret_basic',
  props: {
    appId: 'my-app-123',
    clientType: 'partner'
  }
});

// Props are stored and can be retrieved later
const retrieved = await env.OAUTH_PROVIDER.lookupClient<{ appId: string; environment: string }>(client.clientId);
console.log(retrieved?.props?.appId); // 'my-app-123'

2. Client Registration Callback

Added clientRegistrationCallback option to allow adding props to clients when they are created through the dynamic client registration endpoint.

new OAuthProvider({
  // ... other options ...
  clientRegistrationEndpoint: "https://example.com/oauth/register",
  clientRegistrationCallback: async (options) => {
    // options contains: clientId, redirectUris, clientName, clientUri, props
    return {
      props: {
        appId: generateAppId(),
        environment: determineEnvironment(options.redirectUris),
      }
    };
  }
});

The callback receives parsed client information and can return props to be stored with the client. The callback options are derived from ClientInfo using TypeScript utility types for type safety.

Type Safety

  • ClientInfo now uses a generic type parameter Props instead of Metadata for consistency
  • All client-related methods (lookupClient, createClient, listClients, updateClient, getClient) support the generic Props type
  • ClientRegistrationCallbackOptions is derived from ClientInfo using Pick utility type for type safety

Breaking Changes

None

Testing

  • Added tests for clientRegistrationCallback
  • Added tests for client props via createClient()
  • All existing tests pass (144 tests)

Documentation

  • Updated README with documentation for clientRegistrationCallback

@changeset-bot
Copy link

changeset-bot bot commented Dec 9, 2025

⚠️ No Changeset found

Latest commit: e654a8b

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@DeanMauro
Copy link
Contributor Author

Similar to #96 but I'm more interested in marking clients with metadata that can later be used to identify them. For example if I have a 3rd party app partner register a client with me, they may have unique privileges and should be differentiated from a run-of-the-mill MCP client, e.g.

@kentonv
Copy link
Member

kentonv commented Dec 10, 2025

This seems a bit at odds with the upcoming move to CIMD, in which there will no longer be long-lived registrations stored by the oauth provider.

Perhaps we should instead say that apps should store this in their own separate storage, keyed e.g. by client ID?

@DeanMauro
Copy link
Contributor Author

This seems a bit at odds with the upcoming move to CIMD, in which there will no longer be long-lived registrations stored by the oauth provider.

Perhaps we should instead say that apps should store this in their own separate storage, keyed e.g. by client ID?

This is what I'm currently doing (another entry in KV keyed by the client ID) and was hoping to keep the client info unified in one place. If we're headed to CIMD though, you're right; we'll end up right back at the secondary storage anyway.

Ok, I'll close this one. There's one more feature I'd like to add (RFC 8693 Token Exchange) but will open an issue on that to discuss details before implementing since it's a little beefier.

@DeanMauro DeanMauro closed this Dec 10, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants