[GH-ISSUE #2813] oidc doesnt support oauth spec correctly for mcp #9359

Closed
opened 2026-04-13 04:47:47 -05:00 by GiteaMirror · 4 comments
Owner

Originally created by @zackify on GitHub (May 28, 2025).
Original GitHub issue: https://github.com/better-auth/better-auth/issues/2813

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

i recently got claude max to test integrations that can be added from their web interface directly.

it made me realize two things with the spec that are missing. First, they pass "claudeai" scope to the oauth request.

next, when doing the token exchange, they do not send the client secret. which breaks the token endpoint in oidc as we do not have a way to set a client as public and thus not require the client secret.

When i commented out the client secret requirement in the code, it worked. already i require pkce so its safe.

Solution is to mark a client as public in the oauth table, and then if it is, skip requiring client secret if pkce is on

more info:

According to the OAuth 2.0 specification (RFC 6749), when a client secret is missing during the authorization code exchange at the /token endpoint:

  1. Public clients (e.g., mobile apps, SPAs) are not required to authenticate and can exchange authorization codes without a client secret. They must be registered as public clients.
  2. Confidential clients (e.g., server-side applications) MUST authenticate using their client secret. If missing, the server should return:
    HTTP 401 Unauthorized
    {
    "error": "invalid_client",
    "error_description": "Client authentication failed"
    }
  3. PKCE (RFC 7636) is the recommended approach for public clients - they provide a code_verifier instead of a client secret to prove possession of the authorization code.

The spec requires the authorization server to know each client's type during registration to enforce appropriate authentication requirements.

for now ill manually patch to not require the client secret

its interesting that mcp-remote and the desktop app sends it, but the claude website does not, whcih should still work either way

Current vs. Expected behavior

oauth to work with public client support

What version of Better Auth are you using?

1.2.8

Provide environment information

n/a

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

Backend

Auth config (if applicable)

import { betterAuth } from "better-auth"
export const auth = betterAuth({
  emailAndPassword: {  
    enabled: true
  },
});

Additional context

No response

Originally created by @zackify on GitHub (May 28, 2025). Original GitHub issue: https://github.com/better-auth/better-auth/issues/2813 ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce i recently got claude max to test integrations that can be added from their web interface directly. it made me realize two things with the spec that are missing. First, they pass "claudeai" scope to the oauth request. next, when doing the token exchange, they do not send the client secret. which breaks the token endpoint in oidc as we do not have a way to set a client as public and thus not require the client secret. **When i commented out the client secret requirement in the code, it worked. already i require pkce so its safe.** Solution is to mark a client as public in the oauth table, and then if it is, skip requiring client secret if pkce is on more info: According to the OAuth 2.0 specification (RFC 6749), when a client secret is missing during the authorization code exchange at the /token endpoint: 1. Public clients (e.g., mobile apps, SPAs) are not required to authenticate and can exchange authorization codes without a client secret. They must be registered as public clients. 2. Confidential clients (e.g., server-side applications) MUST authenticate using their client secret. If missing, the server should return: HTTP 401 Unauthorized { "error": "invalid_client", "error_description": "Client authentication failed" } 3. PKCE (RFC 7636) is the recommended approach for public clients - they provide a code_verifier instead of a client secret to prove possession of the authorization code. The spec requires the authorization server to know each client's type during registration to enforce appropriate authentication requirements. for now ill manually patch to not require the client secret its interesting that mcp-remote and the desktop app sends it, but the claude website does not, whcih should still work either way ### Current vs. Expected behavior oauth to work with public client support ### What version of Better Auth are you using? 1.2.8 ### Provide environment information ```bash n/a ``` ### Which area(s) are affected? (Select all that apply) Backend ### Auth config (if applicable) ```typescript import { betterAuth } from "better-auth" export const auth = betterAuth({ emailAndPassword: { enabled: true }, }); ``` ### Additional context _No response_
GiteaMirror added the locked label 2026-04-13 04:47:47 -05:00
Author
Owner

@dkokotov commented on GitHub (Jun 1, 2025):

I wish I had found this issue earlier, as I've spent the last day debugging to come to the exact same conclusion!

I think in general, if code_verifier is present (eg using PKCE), then it's safe to not require client_secret.

Btw @zackify do you show a consent screen on the page configured as the loginPage of the MCP plugin? I noticed also that there is a consentPage in the OIDC config but I am not sure if/when that is every shown.

<!-- gh-comment-id:2927742779 --> @dkokotov commented on GitHub (Jun 1, 2025): I wish I had found this issue earlier, as I've spent the last day debugging to come to the exact same conclusion! I think in general, if code_verifier is present (eg using PKCE), then it's safe to not require client_secret. Btw @zackify do you show a consent screen on the page configured as the `loginPage` of the MCP plugin? I noticed also that there is a `consentPage` in the OIDC config but I am not sure if/when that is every shown.
Author
Owner

@zackify commented on GitHub (Jun 1, 2025):

I had to patch the consent screen logic, my app doesn't need it so I go to a route that just auto accepts consent.

This is fixed but they haven't done a release yet so I still have it patched in my repo.

<!-- gh-comment-id:2927755789 --> @zackify commented on GitHub (Jun 1, 2025): I had to patch the consent screen logic, my app doesn't need it so I go to a route that just auto accepts consent. This is fixed but they haven't done a release yet so I still have it patched in my repo.
Author
Owner

@dkokotov commented on GitHub (Jun 1, 2025):

@zackify thanks.

Btw one other thing I had to fix is, I initially couldn't get it to work with MCP inspector, it would show a "Connection Error".

I eventually figured out it is because MCP inspector does preflight OPTIONS requests for the various oauth provider routes. And even though there is some code in the MCP plugin to set the Allow-* headers (eg https://github.com/better-auth/better-auth/blob/main/packages/better-auth/src/plugins/mcp/index.ts#L725), because those routes are only mounted as GET and POST, this doesn't really help.

I ended up adding this to next.config.js to fix this:

  async headers() {
      return [
          {
              source: '/api/auth/:path*',
              headers: [
                  { key: 'Access-Control-Allow-Credentials', value: 'true' },
                  { key: 'Access-Control-Allow-Origin', value: '*' }, 
                  { key: 'Access-Control-Allow-Methods', value: 'GET,DELETE,PATCH,POST,PUT' },
                  {
                      key: 'Access-Control-Allow-Headers',
                      value: 'X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type'
                  }
              ]
          }
      ];
  }

<!-- gh-comment-id:2927854113 --> @dkokotov commented on GitHub (Jun 1, 2025): @zackify thanks. Btw one other thing I had to fix is, I initially couldn't get it to work with MCP inspector, it would show a "Connection Error". I eventually figured out it is because MCP inspector does preflight OPTIONS requests for the various oauth provider routes. And even though there is some code in the MCP plugin to set the Allow-* headers (eg https://github.com/better-auth/better-auth/blob/main/packages/better-auth/src/plugins/mcp/index.ts#L725), because those routes are only mounted as GET and POST, this doesn't really help. I ended up adding this to `next.config.js` to fix this: ``` async headers() { return [ { source: '/api/auth/:path*', headers: [ { key: 'Access-Control-Allow-Credentials', value: 'true' }, { key: 'Access-Control-Allow-Origin', value: '*' }, { key: 'Access-Control-Allow-Methods', value: 'GET,DELETE,PATCH,POST,PUT' }, { key: 'Access-Control-Allow-Headers', value: 'X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type' } ] } ]; } ```
Author
Owner

@zackify commented on GitHub (Jun 2, 2025):

I'm doing mcp just with the official sdk and better auth oidcprovider.

I'm not using the plugin in better auth, I just use the proxy provider from the typescript sdk.

And then yes I noticed the cors problem too and just added the headers myself in express. It's pretty terrible / annoying the inspector requires this.

Seems to me like the handshake should be done server side in these client tools so we don't have to open up cors to all origins. I don't believe it uses client side at all on the official Claude website, but I'm not sure how other mcp clients handle it.

May be able to set those cors headers only on local testing, but I can't find much information about this and haven't had time to test more.

<!-- gh-comment-id:2930884806 --> @zackify commented on GitHub (Jun 2, 2025): I'm doing mcp just with the official sdk and better auth oidcprovider. I'm not using the plugin in better auth, I just use the proxy provider from the typescript sdk. And then yes I noticed the cors problem too and just added the headers myself in express. It's pretty terrible / annoying the inspector requires this. Seems to me like the handshake should be done server side in these client tools so we don't have to open up cors to all origins. I don't believe it uses client side at all on the official Claude website, but I'm not sure how other mcp clients handle it. May be able to set those cors headers only on local testing, but I can't find much information about this and haven't had time to test more.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#9359