genericOAuth ignores discoveryUrl when generating authorization URL #2324

Closed
opened 2026-03-13 09:44:07 -05:00 by GiteaMirror · 5 comments
Owner

Originally created by @tjx666 on GitHub (Nov 17, 2025).

  • Yes, this is suited for github

To Reproduce

  1. Install better-auth@1.3.34 in a Next.js 16 project.
  2. Enable the genericOAuth plugin with only a discovery URL plus client id/secret, e.g.
genericOAuth({
  config: [
    {
      providerId: 'auth0',
      clientId: process.env.AUTH_AUTH0_ID!,
      clientSecret: process.env.AUTH_AUTH0_SECRET!,
      discoveryUrl: 'https://dev-example.us.auth0.com/.well-known/openid-configuration',
      pkce: true,
    },
  ],
});
  1. Start the dev server and POST /api/auth/sign-in/social with { provider: 'auth0', callbackURL: 'http://localhost:3010/chat' }.
  2. The route responds 500 and the terminal logs TypeError [ERR_INVALID_URL]: Invalid URL from new URL() inside provider.createAuthorizationURL.

Current vs. Expected behavior

Current: signInSocial calls the provider returned by genericOAuth, which always forwards c.authorizationUrl into createAuthorizationURL. When only discoveryUrl is configured (per docs), c.authorizationUrl is undefined, so new URL(undefined) throws TypeError: Invalid URL and the endpoint returns 500.

Expected: If just discoveryUrl is provided, the plugin should fetch authorization_endpoint (and token_endpoint) from the discovery document before calling createAuthorizationURL, matching how the /oauth2/link + callback flows already behave.

Root cause & call path

  1. /api/auth/sign-in/socialsignInSocial (packages/better-auth/src/api/routes/sign-in.ts) looks up the provider from c.context.socialProviders and calls provider.createAuthorizationURL(...).
  2. These providers come from genericOAuth (packages/better-auth/src/plugins/generic-oauth/index.ts). In the genericProviders = options.config.map(...) block the createAuthorizationURL implementation references only c.authorizationUrl and ignores c.discoveryUrl.
  3. When we rely on discovery-only config, c.authorizationUrl stays undefined, so new URL(undefined, ...) throws TypeError [ERR_INVALID_URL] before any redirect happens and /api/auth/sign-in/social returns 500.
  4. The /oauth2/link and callback flows already fetch authorization/token endpoints from discovery, but that logic isn’t reused in the sign-in provider.

What version of Better Auth are you using?

1.3.34 (also reproducible on latest main)

System info

{
  "system": {
    "platform": "darwin",
    "arch": "arm64",
    "version": "Darwin Kernel Version 25.2.0: Tue Nov  4 20:46:48 PST 2025; root:xnu-12377.60.50.501.1~2/RELEASE_ARM64_T8112",
    "release": "25.2.0",
    "cpuCount": 8,
    "cpuModel": "Apple M2",
    "totalMemory": "24.00 GB",
    "freeMemory": "0.28 GB"
  },
  "node": {
    "version": "v24.11.0",
    "env": "development"
  },
  "packageManager": {
    "name": "npm",
    "version": "11.6.2"
  },
  "frameworks": [
    {
      "name": "next",
      "version": "^16.0.3"
    },
    {
      "name": "react",
      "version": "19.2.0"
    }
  ],
  "databases": [
    {
      "name": "pg",
      "version": "^8.16.3"
    },
    {
      "name": "drizzle",
      "version": "^0.44.7"
    },
    {
      "name": "@neondatabase/serverless",
      "version": "^1.0.2"
    }
  ],
  "betterAuth": {
    "version": "file:../better-auth/packages/better-auth",
    "config": {
      "emailAndPassword": {
        "autoSignIn": true,
        "enabled": true,
        "maxPasswordLength": 64,
        "minPasswordLength": 8,
        "requireEmailVerification": true
      },
      "emailVerification": {
        "autoSignInAfterVerification": true,
        "expiresIn": 3600
      },
      "plugins": [
        {
          "name": "generic-oauth",
          "config": {
            "id": "generic-oauth",
            "endpoints": {},
            "$ERROR_CODES": {
              "INVALID_OAUTH_CONFIGURATION": "Invalid OAuth configuration"
            }
          }
        }
      ],
      "socialProviders": {
        "google": {
          "clientId": "[REDACTED]",
          "clientSecret": "[REDACTED]"
        },
        "github": {
          "clientId": "[REDACTED]",
          "clientSecret": "[REDACTED]"
        }
      },
      "user": {
        "fields": {
          "image": "avatar",
          "name": "username"
        },
        "modelName": "users"
      }
    }
  }
}

Which area(s) are affected?

  • Backend
  • Package

Auth config

import { betterAuth } from "better-auth";
import { genericOAuth } from "better-auth/plugins";

export const auth = betterAuth({
  plugins: [
    genericOAuth({
      config: [
        {
          providerId: "auth0",
          clientId: process.env.AUTH_AUTH0_ID!,
          clientSecret: process.env.AUTH_AUTH0_SECRET!,
          discoveryUrl: "https://dev-example.us.auth0.com/.well-known/openid-configuration",
          pkce: true,
        },
      ],
    }),
  ],
});

Additional context

Linking/sign-in works if we manually set authorizationUrl/tokenUrl/userInfoUrl, or if we patch genericOAuth so that its createAuthorizationURL performs the same discovery lookup that /oauth2/link already does. The regression only shows up when relying on discovery-only configs, which the docs recommend.

Originally created by @tjx666 on GitHub (Nov 17, 2025). - [x] Yes, this is suited for github ### To Reproduce 1. Install `better-auth@1.3.34` in a Next.js 16 project. 2. Enable the `genericOAuth` plugin with only a discovery URL plus client id/secret, e.g. ```ts genericOAuth({ config: [ { providerId: 'auth0', clientId: process.env.AUTH_AUTH0_ID!, clientSecret: process.env.AUTH_AUTH0_SECRET!, discoveryUrl: 'https://dev-example.us.auth0.com/.well-known/openid-configuration', pkce: true, }, ], }); ``` 3. Start the dev server and POST `/api/auth/sign-in/social` with { provider: 'auth0', callbackURL: 'http://localhost:3010/chat' }. 4. The route responds 500 and the terminal logs `TypeError [ERR_INVALID_URL]: Invalid URL` from `new URL()` inside `provider.createAuthorizationURL`. ### Current vs. Expected behavior **Current**: `signInSocial` calls the provider returned by `genericOAuth`, which always forwards `c.authorizationUrl` into `createAuthorizationURL`. When only `discoveryUrl` is configured (per docs), `c.authorizationUrl` is `undefined`, so `new URL(undefined)` throws `TypeError: Invalid URL` and the endpoint returns 500. **Expected**: If just `discoveryUrl` is provided, the plugin should fetch `authorization_endpoint` (and `token_endpoint`) from the discovery document before calling `createAuthorizationURL`, matching how the `/oauth2/link` + callback flows already behave. ### Root cause & call path 1. `/api/auth/sign-in/social` → `signInSocial` (packages/better-auth/src/api/routes/sign-in.ts) looks up the provider from `c.context.socialProviders` and calls `provider.createAuthorizationURL(...)`. 2. These providers come from `genericOAuth` (packages/better-auth/src/plugins/generic-oauth/index.ts). In the `genericProviders = options.config.map(...)` block the `createAuthorizationURL` implementation references only `c.authorizationUrl` and ignores `c.discoveryUrl`. 3. When we rely on discovery-only config, `c.authorizationUrl` stays `undefined`, so `new URL(undefined, ...)` throws `TypeError [ERR_INVALID_URL]` before any redirect happens and `/api/auth/sign-in/social` returns 500. 4. The `/oauth2/link` and callback flows already fetch authorization/token endpoints from discovery, but that logic isn’t reused in the sign-in provider. ### What version of Better Auth are you using? `1.3.34` (also reproducible on latest main) ### System info ```json { "system": { "platform": "darwin", "arch": "arm64", "version": "Darwin Kernel Version 25.2.0: Tue Nov 4 20:46:48 PST 2025; root:xnu-12377.60.50.501.1~2/RELEASE_ARM64_T8112", "release": "25.2.0", "cpuCount": 8, "cpuModel": "Apple M2", "totalMemory": "24.00 GB", "freeMemory": "0.28 GB" }, "node": { "version": "v24.11.0", "env": "development" }, "packageManager": { "name": "npm", "version": "11.6.2" }, "frameworks": [ { "name": "next", "version": "^16.0.3" }, { "name": "react", "version": "19.2.0" } ], "databases": [ { "name": "pg", "version": "^8.16.3" }, { "name": "drizzle", "version": "^0.44.7" }, { "name": "@neondatabase/serverless", "version": "^1.0.2" } ], "betterAuth": { "version": "file:../better-auth/packages/better-auth", "config": { "emailAndPassword": { "autoSignIn": true, "enabled": true, "maxPasswordLength": 64, "minPasswordLength": 8, "requireEmailVerification": true }, "emailVerification": { "autoSignInAfterVerification": true, "expiresIn": 3600 }, "plugins": [ { "name": "generic-oauth", "config": { "id": "generic-oauth", "endpoints": {}, "$ERROR_CODES": { "INVALID_OAUTH_CONFIGURATION": "Invalid OAuth configuration" } } } ], "socialProviders": { "google": { "clientId": "[REDACTED]", "clientSecret": "[REDACTED]" }, "github": { "clientId": "[REDACTED]", "clientSecret": "[REDACTED]" } }, "user": { "fields": { "image": "avatar", "name": "username" }, "modelName": "users" } } } } ``` ### Which area(s) are affected? - Backend - Package ### Auth config ```ts import { betterAuth } from "better-auth"; import { genericOAuth } from "better-auth/plugins"; export const auth = betterAuth({ plugins: [ genericOAuth({ config: [ { providerId: "auth0", clientId: process.env.AUTH_AUTH0_ID!, clientSecret: process.env.AUTH_AUTH0_SECRET!, discoveryUrl: "https://dev-example.us.auth0.com/.well-known/openid-configuration", pkce: true, }, ], }), ], }); ``` ### Additional context Linking/sign-in works if we manually set `authorizationUrl/tokenUrl/userInfoUrl`, or if we patch `genericOAuth` so that its `createAuthorizationURL` performs the same discovery lookup that `/oauth2/link` already does. The regression only shows up when relying on discovery-only configs, which the docs recommend.
GiteaMirror added the plugin label 2026-03-13 09:44:07 -05:00
Author
Owner

@better-auth-agent[bot] commented on GitHub (Nov 17, 2025):

(tag @better-auth-agent if you would like an answer from the Agent)

DiagramDiscordGitHub

Diagram Join Star

@better-auth-agent[bot] commented on GitHub (Nov 17, 2025): (tag @better-auth-agent if you would like an answer from the Agent) <!-- bot:webhook reply v1 --> [Diagram](https://repodiagrams.s3.eu-north-1.amazonaws.com/better-auth_ultra_detailed_interactive.html) • [Discord](https://discord.gg/better-auth) • [GitHub](https://github.com/better-auth/better-auth) [![Diagram](https://img.shields.io/badge/Diagram-2b3137?style=flat-square)](https://repodiagrams.s3.eu-north-1.amazonaws.com/better-auth_ultra_detailed_interactive.html) [![Join](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&style=flat-square)](https://discord.gg/better-auth) [![Star](https://img.shields.io/badge/star-181717?logo=github&logoColor=white&style=flat-square)](https://github.com/better-auth/better-auth)
Author
Owner

@dosubot[bot] commented on GitHub (Nov 17, 2025):

This is a known limitation in the genericOAuth plugin: when you configure only a discoveryUrl (without explicit authorizationUrl/tokenUrl), the provider's createAuthorizationURL method does not fetch endpoints from the discovery document. Instead, it tries to use authorizationUrl directly from the config, which is undefined in this case—causing the TypeError you saw. This only affects flows like sign-in/social that use the provider directly; other flows (like /oauth2/link or /sign-in/oauth2) do perform discovery and work as expected with discovery-only configs.

Workarounds: manually set authorizationUrl, tokenUrl, and userInfoUrl in your config, or patch genericOAuth to perform discovery lookup in createAuthorizationURL, matching the behavior of the other flows. This behavior is documented in several issues and is not yet auto-resolved by the plugin itself [1] [2] [3].

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other  Chat with Dosu Join Discord Share on X

@dosubot[bot] commented on GitHub (Nov 17, 2025): <!-- Answer --> This is a known limitation in the genericOAuth plugin: when you configure only a discoveryUrl (without explicit authorizationUrl/tokenUrl), the provider's createAuthorizationURL method does not fetch endpoints from the discovery document. Instead, it tries to use authorizationUrl directly from the config, which is undefined in this case—causing the TypeError you saw. This only affects flows like sign-in/social that use the provider directly; other flows (like /oauth2/link or /sign-in/oauth2) do perform discovery and work as expected with discovery-only configs. Workarounds: manually set authorizationUrl, tokenUrl, and userInfoUrl in your config, or patch genericOAuth to perform discovery lookup in createAuthorizationURL, matching the behavior of the other flows. This behavior is documented in several issues and is not yet auto-resolved by the plugin itself [[1](https://github.com/better-auth/better-auth/issues/3278)] [[2](https://github.com/better-auth/better-auth/issues/5773)] [[3](https://github.com/better-auth/better-auth/blob/d768852258d1fea921984196806608af2983a867/packages/better-auth/src/plugins/generic-oauth/index.ts#L28-L975)]. <!-- 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/fc5971db-6344-4627-8e8f-195ff1297070?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/fc5971db-6344-4627-8e8f-195ff1297070?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/fc5971db-6344-4627-8e8f-195ff1297070?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/fc5971db-6344-4627-8e8f-195ff1297070?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/fc5971db-6344-4627-8e8f-195ff1297070?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/fc5971db-6344-4627-8e8f-195ff1297070?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/fc5971db-6344-4627-8e8f-195ff1297070?feedback_type=other)</sup>&nbsp;&nbsp;[![Chat with Dosu](https://dosu.dev/dosu-chat-badge.svg)](https://app.dosu.dev/cdda13d9-dd27-4d31-b09a-5d8bec92de21/ask?utm_source=github)&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/6042)
Author
Owner

@aheidelberg commented on GitHub (Nov 17, 2025):

I'm getting the same error, not with generic oauth but with SSO and an OIDC provider (Microsoft Entra).
Is there a reason better-auth is not fetching the endpoints from the discovery URL if they are not explicitly provided in the config?

@aheidelberg commented on GitHub (Nov 17, 2025): I'm getting the same error, not with generic oauth but with SSO and an OIDC provider (Microsoft Entra). Is there a reason better-auth is not fetching the endpoints from the discovery URL if they are not explicitly provided in the config?
Author
Owner

@GautamBytes commented on GitHub (Nov 17, 2025):

will be working on it!

@GautamBytes commented on GitHub (Nov 17, 2025): will be working on it!
Author
Owner

@himself65 commented on GitHub (Nov 17, 2025):

will be working on it!

i would appreciate it

@himself65 commented on GitHub (Nov 17, 2025): > will be working on it! i would appreciate it
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#2324