oauth-provider should support unauthenticated confidential DCR for client_secret_post / client_secret_basic #3040

Open
opened 2026-03-13 10:35:45 -05:00 by GiteaMirror · 0 comments
Owner

Originally created by @romaincointepas on GitHub (Mar 13, 2026).

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

  1. Configure oauth-provider with:

    allowDynamicClientRegistration: true,
    allowUnauthenticatedClientRegistration: true,
    
  2. Use an OAuth client that attempts unauthenticated dynamic client registration
    as a confidential client, for example this real payload from Claude custom
    connectors:

    {
      "redirect_uris": ["https://claude.ai/api/mcp/auth_callback"],
      "token_endpoint_auth_method": "client_secret_post",
      "grant_types": ["authorization_code", "refresh_token"],
      "response_types": ["code"],
      "scope": "offline_access mcp:tools",
      "client_name": "Claude"
    }
    
  3. POST that payload to /auth/oauth2/register.

  4. Observe that the registration fails with:

    401 Unauthorized
    
  5. Change only:

    "token_endpoint_auth_method": "none"
    

    and retry the same registration flow.

  6. Observe that registration succeeds as an unauthenticated public client.

Current vs. Expected behavior

Current behavior:

  • oauth-provider advertises client_secret_basic and
    client_secret_post in token_endpoint_auth_methods_supported
  • but unauthenticated dynamic client registration only works when
    token_endpoint_auth_method === "none"
  • if the client tries unauthenticated registration with
    client_secret_post or client_secret_basic, the provider returns
    401 Unauthorized

Expected behavior:

  • either oauth-provider should support unauthenticated confidential
    dynamic client registration for at least:
    • client_secret_post
    • client_secret_basic
  • or the package should clearly document that unauthenticated DCR is
    public-client-only, even though those confidential token endpoint auth
    methods are advertised in metadata

Why this is confusing:

Metadata currently advertises:

token_endpoint_auth_methods_supported: [
  ...(overrides?.public_client_supported
    ? (["none"] satisfies TokenEndpointAuthMethod[])
    : []),
  "client_secret_basic",
  "client_secret_post",
],

Source:

But the registration implementation rejects unauthenticated clients unless
they are public clients (token_endpoint_auth_method === "none"), so hosted
OAuth clients like Claude custom connectors fail even when
allowUnauthenticatedClientRegistration is enabled.

What version of Better Auth are you using?

1.5.5

System info

{
  "system": {
    "platform": "darwin",
    "arch": "arm64",
    "version": "Darwin Kernel Version 25.3.0: Wed Jan 28 20:53:15 PST 2026; root:xnu-12377.81.4~5/RELEASE_ARM64_T6000",
    "release": "25.3.0",
    "cpuCount": 10,
    "cpuModel": "Apple M1 Pro",
    "totalMemory": "32.00 GB",
    "freeMemory": "0.36 GB"
  },
  "node": {
    "version": "v24.14.0",
    "env": "development"
  },
  "packageManager": {
    "name": "npm",
    "version": "11.9.0"
  },
  "frameworks": [
    {
      "name": "next",
      "version": "16.2.0-canary.95"
    },
    {
      "name": "react",
      "version": "^19.2.4"
    }
  ],
  "databases": [
    {
      "name": "pg",
      "version": "^8.20.0"
    }
  ],
  "betterAuth": {
    "version": "Unknown",
    "config": null,
    "error": "Converting circular structure to JSON\n    --> starting at object with constructor 'Stripe'\n    |     property 'account' -> object with constructor 'Constructor'\n    --- property '_stripe' closes the circle"
  }
}

Which area(s) are affected? (Select all that apply)

Backend, Package

Auth config (if applicable)

import { betterAuth } from "better-auth";
import { oauthProvider } from "@better-auth/oauth-provider";

export const auth = betterAuth({
  plugins: [
    oauthProvider({
      allowDynamicClientRegistration: true,
      allowUnauthenticatedClientRegistration: true,
      scopes: ["offline_access", "mcp:tools"],
      clientRegistrationAllowedScopes: ["offline_access", "mcp:tools"],
      clientRegistrationDefaultScopes: ["offline_access"],
    }),
  ],
});

Additional context

  • This surfaced while integrating a remote MCP server with Claude custom
    connectors.

  • The failing registration payload is a real request captured from Claude:

    {
      "redirect_uris": ["https://claude.ai/api/mcp/auth_callback"],
      "token_endpoint_auth_method": "client_secret_post",
      "grant_types": ["authorization_code", "refresh_token"],
      "response_types": ["code"],
      "scope": "offline_access mcp:tools",
      "client_name": "Claude"
    }
    
  • Public DCR works correctly in the same app when the client sends
    token_endpoint_auth_method: "none".

  • This looks related to the general metadata/behavior mismatch discussed in:

  • npx auth info --json reports betterAuth.version as Unknown in this app
    because config serialization hits a circular Stripe object, but both
    better-auth and @better-auth/oauth-provider installed versions are
    1.5.5.

  • I am not saying the MCP spec requires unauthenticated confidential DCR, only
    that hosted OAuth clients in the wild use this pattern and currently fail
    against oauth-provider.

Originally created by @romaincointepas on GitHub (Mar 13, 2026). ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce 1. Configure `oauth-provider` with: ```ts allowDynamicClientRegistration: true, allowUnauthenticatedClientRegistration: true, ``` 2. Use an OAuth client that attempts unauthenticated dynamic client registration as a confidential client, for example this real payload from Claude custom connectors: ```json { "redirect_uris": ["https://claude.ai/api/mcp/auth_callback"], "token_endpoint_auth_method": "client_secret_post", "grant_types": ["authorization_code", "refresh_token"], "response_types": ["code"], "scope": "offline_access mcp:tools", "client_name": "Claude" } ``` 3. POST that payload to `/auth/oauth2/register`. 4. Observe that the registration fails with: ```http 401 Unauthorized ``` 5. Change only: ```json "token_endpoint_auth_method": "none" ``` and retry the same registration flow. 6. Observe that registration succeeds as an unauthenticated public client. ### Current vs. Expected behavior Current behavior: - `oauth-provider` advertises `client_secret_basic` and `client_secret_post` in `token_endpoint_auth_methods_supported` - but unauthenticated dynamic client registration only works when `token_endpoint_auth_method === "none"` - if the client tries unauthenticated registration with `client_secret_post` or `client_secret_basic`, the provider returns `401 Unauthorized` Expected behavior: - either `oauth-provider` should support unauthenticated confidential dynamic client registration for at least: - `client_secret_post` - `client_secret_basic` - or the package should clearly document that unauthenticated DCR is public-client-only, even though those confidential token endpoint auth methods are advertised in metadata Why this is confusing: Metadata currently advertises: ```ts token_endpoint_auth_methods_supported: [ ...(overrides?.public_client_supported ? (["none"] satisfies TokenEndpointAuthMethod[]) : []), "client_secret_basic", "client_secret_post", ], ``` Source: - https://github.com/better-auth/better-auth/blob/0ba16ccbbc99035453fffbcdf8f2cd37fac0f1cf/packages/oauth-provider/src/metadata.ts#L52 But the registration implementation rejects unauthenticated clients unless they are public clients (`token_endpoint_auth_method === "none"`), so hosted OAuth clients like Claude custom connectors fail even when `allowUnauthenticatedClientRegistration` is enabled. ### What version of Better Auth are you using? 1.5.5 ### System info ```bash { "system": { "platform": "darwin", "arch": "arm64", "version": "Darwin Kernel Version 25.3.0: Wed Jan 28 20:53:15 PST 2026; root:xnu-12377.81.4~5/RELEASE_ARM64_T6000", "release": "25.3.0", "cpuCount": 10, "cpuModel": "Apple M1 Pro", "totalMemory": "32.00 GB", "freeMemory": "0.36 GB" }, "node": { "version": "v24.14.0", "env": "development" }, "packageManager": { "name": "npm", "version": "11.9.0" }, "frameworks": [ { "name": "next", "version": "16.2.0-canary.95" }, { "name": "react", "version": "^19.2.4" } ], "databases": [ { "name": "pg", "version": "^8.20.0" } ], "betterAuth": { "version": "Unknown", "config": null, "error": "Converting circular structure to JSON\n --> starting at object with constructor 'Stripe'\n | property 'account' -> object with constructor 'Constructor'\n --- property '_stripe' closes the circle" } } ``` ### Which area(s) are affected? (Select all that apply) Backend, Package ### Auth config (if applicable) ```typescript import { betterAuth } from "better-auth"; import { oauthProvider } from "@better-auth/oauth-provider"; export const auth = betterAuth({ plugins: [ oauthProvider({ allowDynamicClientRegistration: true, allowUnauthenticatedClientRegistration: true, scopes: ["offline_access", "mcp:tools"], clientRegistrationAllowedScopes: ["offline_access", "mcp:tools"], clientRegistrationDefaultScopes: ["offline_access"], }), ], }); ``` ### Additional context - This surfaced while integrating a remote MCP server with Claude custom connectors. - The failing registration payload is a real request captured from Claude: ```json { "redirect_uris": ["https://claude.ai/api/mcp/auth_callback"], "token_endpoint_auth_method": "client_secret_post", "grant_types": ["authorization_code", "refresh_token"], "response_types": ["code"], "scope": "offline_access mcp:tools", "client_name": "Claude" } ``` - Public DCR works correctly in the same app when the client sends `token_endpoint_auth_method: "none"`. - This looks related to the general metadata/behavior mismatch discussed in: - https://github.com/better-auth/better-auth/issues/8423 - https://github.com/better-auth/better-auth/issues/4461 - `npx auth info --json` reports `betterAuth.version` as `Unknown` in this app because config serialization hits a circular `Stripe` object, but both `better-auth` and `@better-auth/oauth-provider` installed versions are `1.5.5`. - I am not saying the MCP spec requires unauthenticated confidential DCR, only that hosted OAuth clients in the wild use this pattern and currently fail against `oauth-provider`.
GiteaMirror added the enhancementplugin labels 2026-03-13 10:35:45 -05:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#3040