OAuth AS metadata URL resolution fails when issuer includes a path prefix (RFC 8414 §3.1) #2974

Open
opened 2026-03-13 10:31:33 -05:00 by GiteaMirror · 1 comment
Owner

Originally created by @subsetpark on GitHub (Mar 3, 2026).

Description

When baseURL is set (e.g. https://example.com), the OAuth provider plugin sets the issuer to {baseURL}/api/auth (appending the Better Auth base path). This issuer value is also used as the authorization_servers entry in the protected resource metadata response (/.well-known/oauth-protected-resource).

Per RFC 8414 §3.1, clients resolve the authorization server metadata by requesting:

{issuer}/.well-known/oauth-authorization-server

So when the issuer is https://example.com/api/auth, clients request:

https://example.com/api/auth/.well-known/oauth-authorization-server  → 404

Better Auth serves the metadata at the path-based form instead:

https://example.com/.well-known/oauth-authorization-server/api/auth  → 200 ✅

Both URL forms are defined in RFC 8414, but the direct form ({issuer}/.well-known/...) is the one most clients implement. This breaks MCP OAuth for clients like Claude.ai that use the direct form.

Steps to reproduce

  1. Configure Better Auth with baseURL: 'https://example.com' and the oauthProvider plugin
  2. Fetch https://example.com/.well-known/oauth-protected-resource — note authorization_servers is ["https://example.com/api/auth"]
  3. Request https://example.com/api/auth/.well-known/oauth-authorization-server404

Expected behavior

Either:

  • Serve the AS metadata at {issuer}/.well-known/oauth-authorization-server (the direct form), or
  • Set authorization_servers to the origin without the path prefix so the standard well-known resolution works

Workaround

We added a Next.js rewrite to map the direct form to the path-based form:

// next.config.ts
rewrites: async () => ({
  beforeFiles: [
    {
      source: '/api/auth/.well-known/:path*',
      destination: '/.well-known/:path*/api/auth',
    },
  ],
})

Environment

  • better-auth: 1.2.16
  • @better-auth/oauth-provider: 1.2.16
  • Framework: Next.js 16 (App Router)
Originally created by @subsetpark on GitHub (Mar 3, 2026). ## Description When `baseURL` is set (e.g. `https://example.com`), the OAuth provider plugin sets the issuer to `{baseURL}/api/auth` (appending the Better Auth base path). This issuer value is also used as the `authorization_servers` entry in the protected resource metadata response (`/.well-known/oauth-protected-resource`). Per [RFC 8414 §3.1](https://datatracker.ietf.org/doc/html/rfc8414#section-3.1), clients resolve the authorization server metadata by requesting: ``` {issuer}/.well-known/oauth-authorization-server ``` So when the issuer is `https://example.com/api/auth`, clients request: ``` https://example.com/api/auth/.well-known/oauth-authorization-server → 404 ``` Better Auth serves the metadata at the **path-based** form instead: ``` https://example.com/.well-known/oauth-authorization-server/api/auth → 200 ✅ ``` Both URL forms are defined in RFC 8414, but the direct form (`{issuer}/.well-known/...`) is the one most clients implement. This breaks MCP OAuth for clients like Claude.ai that use the direct form. ## Steps to reproduce 1. Configure Better Auth with `baseURL: 'https://example.com'` and the `oauthProvider` plugin 2. Fetch `https://example.com/.well-known/oauth-protected-resource` — note `authorization_servers` is `["https://example.com/api/auth"]` 3. Request `https://example.com/api/auth/.well-known/oauth-authorization-server` → **404** ## Expected behavior Either: - Serve the AS metadata at `{issuer}/.well-known/oauth-authorization-server` (the direct form), or - Set `authorization_servers` to the origin without the path prefix so the standard well-known resolution works ## Workaround We added a Next.js rewrite to map the direct form to the path-based form: ```typescript // next.config.ts rewrites: async () => ({ beforeFiles: [ { source: '/api/auth/.well-known/:path*', destination: '/.well-known/:path*/api/auth', }, ], }) ``` ## Environment - `better-auth`: 1.2.16 - `@better-auth/oauth-provider`: 1.2.16 - Framework: Next.js 16 (App Router)
GiteaMirror added the nextjsbug labels 2026-03-13 10:31:33 -05:00
Author
Owner

@dosubot[bot] commented on GitHub (Mar 3, 2026):

Hi @subsetpark! I'm Dosu and I’m helping the better-auth team.

This is a known issue tracked in #7453. The root cause is that Better Auth's router applies basePath uniformly to all endpoints, including well-known paths, rather than mounting them at the origin root as RFC 8414 §3.1 requires.

Proposed solution: PR #7524 introduces a publicEndpoints plugin capability that would mount well-known endpoints at the origin root instead of under basePath, with zero impact on existing behavior.

Your Next.js rewrite workaround is the recommended approach until the PR is merged. The plugin also prints warnings during initialization suggesting this reverse-proxy configuration.

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other

Chat with Dosu Open in Cursor Join Discord Share on X

@dosubot[bot] commented on GitHub (Mar 3, 2026): <!-- Greeting --> Hi @subsetpark! I'm [Dosu](https://go.dosu.dev/dosubot) and I’m helping the better-auth team. <!-- Answer --> This is a known issue tracked in [#7453](https://github.com/better-auth/better-auth/issues/7453). The root cause is that Better Auth's router [applies basePath uniformly to all endpoints](https://github.com/better-auth/better-auth/blob/54c84939ff6498d90a7c1147b4ade0061ed6c08d/packages/better-auth/src/api/index.ts#L257-L270), including well-known paths, rather than mounting them at the origin root as RFC 8414 §3.1 requires. **Proposed solution**: [PR #7524](https://github.com/better-auth/better-auth/pull/7524) introduces a `publicEndpoints` plugin capability that would mount well-known endpoints at the origin root instead of under basePath, with zero impact on existing behavior. Your Next.js rewrite workaround is the recommended approach until the PR is merged. The plugin also [prints warnings during initialization](https://github.com/better-auth/better-auth/blob/54c84939ff6498d90a7c1147b4ade0061ed6c08d/packages/oauth-provider/src/oauth.ts#L174-L192) suggesting this reverse-proxy configuration. <!-- Dosu Comment Footer --> *To reply, just mention [@dosu](https://go.dosu.dev/dosubot).* --- <sup>How did I do? [Good](https://app.dosu.dev/response-feedback/68334fed-2fbe-4718-9029-e701eb200f95?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/68334fed-2fbe-4718-9029-e701eb200f95?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/68334fed-2fbe-4718-9029-e701eb200f95?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/68334fed-2fbe-4718-9029-e701eb200f95?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/68334fed-2fbe-4718-9029-e701eb200f95?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/68334fed-2fbe-4718-9029-e701eb200f95?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/68334fed-2fbe-4718-9029-e701eb200f95?feedback_type=other)</sup> [![Chat with Dosu](https://dosu.dev/dosu-chat-badge.svg)](https://app.dosu.dev/cdda13d9-dd27-4d31-b09a-5d8bec92de21/ask?utm_source=github)&nbsp;[![Open in Cursor](https://dosu.dev/dosu-in-cursor.svg)](https://cursor.com/link/prompt?text=This%20is%20a%20known%20issue%20tracked%20in%20%5B%237453%5D%28https%3A//github.com/better-auth/better-auth/issues/7453%29.%20The%20root%20cause%20is%20that%20Better%20Auth%27s%20router%20%5Bapplies%20basePath%20uniformly%20to%20all%20endpoints%5D%28https%3A//github.com/better-auth/better-auth/blob/54c84939ff6498d90a7c1147b4ade0061ed6c08d/packages/better-auth/src/api/index.ts%23L257-L270%29%2C%20including%20well-known%20paths%2C%20rather%20than%20mounting%20them%20at%20the%20origin%20root%20as%20RFC%208414%20%C2%A73.1%20requires.%0A%0A%2A%2AProposed%20solution%2A%2A%3A%20%5BPR%20%237524%5D%28https%3A//github.com/better-auth/better-auth/pull/7524%29%20introduces%20a%20%60publicEndpoints%60%20plugin%20capability%20that%20would%20mount%20well-known%20endpoints%20at%20the%20origin%20root%20instead%20of%20under%20basePath%2C%20with%20zero%20impact%20on%20existing%20behavior.%0A%0AYour%20Next.js%20rewrite%20workaround%20is%20the%20recommended%20approach%20until%20the%20PR%20is%20merged.%20The%20plugin%20also%20%5Bprints%20warnings%20during%20initialization%5D%28https%3A//github.com/better-auth/better-auth/blob/54c84939ff6498d90a7c1147b4ade0061ed6c08d/packages/oauth-provider/src/oauth.ts%23L174-L192%29%20suggesting%20this%20reverse-proxy%20configuration.)&nbsp;[![Join Discord](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&label=)](https://go.dosu.dev/discord-bot)&nbsp;[![Share on X](https://img.shields.io/badge/X-share-black)](https://twitter.com/intent/tweet?text=%40dosu_ai%20helped%20me%20solve%20this%20issue!&url=https%3A//github.com/better-auth/better-auth/issues/8343)
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#2974