[oidcProvider] trustedClients don't get added to the OauthApplication table in the database. #2065

Closed
opened 2026-03-13 09:24:14 -05:00 by GiteaMirror · 3 comments
Owner

Originally created by @pxtrickb on GitHub (Oct 2, 2025).

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

Set up two Next.js apps, both with better-auth.

One serves as an oidc provider (https://idapp.appname.local), with the oidcProvider plugin installed.
Here is the config:

oidcProvider({
        loginPage: "/signin",
        consentPage: "/consent",
        metadata: {
            issuer: "https://idapp.appname.local",
            authorization_endpoint: "/oauth2/authorize",
            token_endpoint: "/oauth2/token",
            jwks_uri: "/.well-known/jwks.json"
        },
        useJWTPlugin: true,
        allowDynamicClientRegistration: false,
        trustedClients: [
            {
                clientId: "clientApp",
                clientSecret: "xxx",
                name: "Client App",
                type: "web",
                redirectURLs: ["https://clientapp.appname.local/api/auth/oauth2/callback/idApp"],
                skipConsent: true,
                disabled: false,
                metadata: {}
            }
        ],
    })

The other serves as a consumer of the oidc provider (https://clientapp.appname.local), with the generic oAuth plugin installed.
Here is the config:

genericOAuth({
            config: [
                {
                    providerId: "idApp",
                    clientId: "clientApp",
                    clientSecret: "xxx",
                    authorizationUrl: "https://idapp.appname.local/api/auth/oauth2/authorize",
                    tokenUrl: "https://idapp.appname.local/api/auth/oauth2/token",
                    userInfoUrl: "https://idapp.appname.local/api/auth/oauth2/userinfo",
                    scopes: ["openid", "email", "profile"],
                },
            ]
        })

Current vs. Expected behavior

When you try to run the following code from the client app:

const { data, error } = await authClient.signIn.oauth2({
      providerId: "idApp",
      callbackURL: "/",
      errorCallbackURL: "/",
      disableRedirect: false,
      scopes: ["openid", "email", "profile"],
    });

You get the following error: oauth_code_verification_failed
Furthermore, in the console of the idApp, you get the following logs:

2025-10-02T15:34:27.784Z ERROR [Better Auth]: PrismaClientKnownRequestError Error [PrismaClientKnownRequestError]: Invalid prisma.oauthAccessToken.create() invocation: Foreign key constraint violated on the foreign key

What version of Better Auth are you using?

1.3.24

System info

{
  "system": {
    "platform": "win32",
    "arch": "x64",
    "version": "Windows 11 Pro",
    "release": "10.0.26100",
    "cpuCount": 20,
    "cpuModel": "12th Gen Intel(R) Core(TM) i7-12700H",
    "totalMemory": "15.62 GB",
    "freeMemory": "1.88 GB"
  },
  "node": {
    "version": "v24.0.2",
    "env": "development"
  },
  "packageManager": {
    "name": "npm",
    "version": "11.4.1"
  },
  "frameworks": [
    {
      "name": "next",
      "version": "15.5.4"
    },
    {
      "name": "react",
      "version": "19.1.0"
    }
  ],
  "databases": [
    {
      "name": "@prisma/client",
      "version": "^6.16.3"
    }
  ],
  "betterAuth": {
    "version": "^1.3.24",
    "config": {
      "emailAndPassword": {
        "enabled": true,
        "requireEmailVerification": false
      },
      "plugins": [
        {
          "name": "oidc",
          "config": {
            "id": "oidc",
            "hooks": {
              "after": [
                {}
              ]
            },
            "endpoints": {},
            "schema": {
              "oauthApplication": {
                "modelName": "oauthApplication",
                "fields": {
                  "name": {
                    "type": "string"
                  },
                  "icon": {
                    "type": "string",
                    "required": false
                  },
                  "metadata": {
                    "type": "string",
                    "required": false
                  },
                  "clientId": {
                    "type": "string",
                    "unique": true
                  },
                  "clientSecret": {
                    "type": "string",
                    "required": false
                  },
                  "redirectURLs": {
                    "type": "string"
                  },
                  "type": {
                    "type": "string"
                  },
                  "disabled": {
                    "type": "boolean",
                    "required": false,
                    "defaultValue": false
                  },
                  "userId": {
                    "type": "string",
                    "required": false,
                    "references": {
                      "model": "user",
                      "field": "id",
                      "onDelete": "cascade"
                    }
                  },
                  "createdAt": {
                    "type": "date"
                  },
                  "updatedAt": {
                    "type": "date"
                  }
                }
              },
              "oauthAccessToken": {
                "modelName": "oauthAccessToken",
                "fields": {
                  "accessToken": {
                    "type": "string",
                    "unique": true
                  },
                  "refreshToken": {
                    "type": "string",
                    "unique": true
                  },
                  "accessTokenExpiresAt": {
                    "type": "date"
                  },
                  "refreshTokenExpiresAt": {
                    "type": "date"
                  },
                  "clientId": {
                    "type": "string",
                    "references": {
                      "model": "oauthApplication",
                      "field": "clientId",
                      "onDelete": "cascade"
                    }
                  },
                  "userId": {
                    "type": "string",
                    "required": false,
                    "references": {
                      "model": "user",
                      "field": "id",
                      "onDelete": "cascade"
                    }
                  },
                  "scopes": {
                    "type": "string"
                  },
                  "createdAt": {
                    "type": "date"
                  },
                  "updatedAt": {
                    "type": "date"
                  }
                }
              },
              "oauthConsent": {
                "modelName": "oauthConsent",
                "fields": {
                  "clientId": {
                    "type": "string",
                    "references": {
                      "model": "oauthApplication",
                      "field": "clientId",
                      "onDelete": "cascade"
                    }
                  },
                  "userId": {
                    "type": "string",
                    "references": {
                      "model": "user",
                      "field": "id",
                      "onDelete": "cascade"
                    }
                  },
                  "scopes": {
                    "type": "string"
                  },
                  "createdAt": {
                    "type": "date"
                  },
                  "updatedAt": {
                    "type": "date"
                  },
                  "consentGiven": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        }
      ],
      "cookieCache": {
        "enabled": true,
        "maxAge": 300
      },
      "advanced": {
        "cookiePrefix": "idapp"
      },
      "trustedOrigins": [
        "https://idapp.appname.local",
        "https://clientapp.appname.local"
      ],
      "secret": "[REDACTED]",
      "url": "http://localhost:3001"
    }
  }
}

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

Backend

Auth config (if applicable)


Additional context

After some investigation, I noticed that the OauthApplications table in the database is empty.
Even after running npx @better-auth/cli generate followed by npx prisma db push,
the table is still empty.

It seems as if the trustedClients: object in the oidcProvider's config should automatically add these records to the database, but it does not.

Originally created by @pxtrickb on GitHub (Oct 2, 2025). ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce Set up two Next.js apps, both with better-auth. One serves as an oidc provider (https://idapp.appname.local), with the oidcProvider plugin installed. Here is the config: ```ts oidcProvider({ loginPage: "/signin", consentPage: "/consent", metadata: { issuer: "https://idapp.appname.local", authorization_endpoint: "/oauth2/authorize", token_endpoint: "/oauth2/token", jwks_uri: "/.well-known/jwks.json" }, useJWTPlugin: true, allowDynamicClientRegistration: false, trustedClients: [ { clientId: "clientApp", clientSecret: "xxx", name: "Client App", type: "web", redirectURLs: ["https://clientapp.appname.local/api/auth/oauth2/callback/idApp"], skipConsent: true, disabled: false, metadata: {} } ], }) ``` The other serves as a consumer of the oidc provider (https://clientapp.appname.local), with the generic oAuth plugin installed. Here is the config: ```ts genericOAuth({ config: [ { providerId: "idApp", clientId: "clientApp", clientSecret: "xxx", authorizationUrl: "https://idapp.appname.local/api/auth/oauth2/authorize", tokenUrl: "https://idapp.appname.local/api/auth/oauth2/token", userInfoUrl: "https://idapp.appname.local/api/auth/oauth2/userinfo", scopes: ["openid", "email", "profile"], }, ] }) ``` ### Current vs. Expected behavior When you try to run the following code from the client app: ```ts const { data, error } = await authClient.signIn.oauth2({ providerId: "idApp", callbackURL: "/", errorCallbackURL: "/", disableRedirect: false, scopes: ["openid", "email", "profile"], }); ``` You get the following error: `oauth_code_verification_failed` Furthermore, in the console of the idApp, you get the following logs: ``` 2025-10-02T15:34:27.784Z ERROR [Better Auth]: PrismaClientKnownRequestError Error [PrismaClientKnownRequestError]: Invalid prisma.oauthAccessToken.create() invocation: Foreign key constraint violated on the foreign key ``` ### What version of Better Auth are you using? 1.3.24 ### System info ```bash { "system": { "platform": "win32", "arch": "x64", "version": "Windows 11 Pro", "release": "10.0.26100", "cpuCount": 20, "cpuModel": "12th Gen Intel(R) Core(TM) i7-12700H", "totalMemory": "15.62 GB", "freeMemory": "1.88 GB" }, "node": { "version": "v24.0.2", "env": "development" }, "packageManager": { "name": "npm", "version": "11.4.1" }, "frameworks": [ { "name": "next", "version": "15.5.4" }, { "name": "react", "version": "19.1.0" } ], "databases": [ { "name": "@prisma/client", "version": "^6.16.3" } ], "betterAuth": { "version": "^1.3.24", "config": { "emailAndPassword": { "enabled": true, "requireEmailVerification": false }, "plugins": [ { "name": "oidc", "config": { "id": "oidc", "hooks": { "after": [ {} ] }, "endpoints": {}, "schema": { "oauthApplication": { "modelName": "oauthApplication", "fields": { "name": { "type": "string" }, "icon": { "type": "string", "required": false }, "metadata": { "type": "string", "required": false }, "clientId": { "type": "string", "unique": true }, "clientSecret": { "type": "string", "required": false }, "redirectURLs": { "type": "string" }, "type": { "type": "string" }, "disabled": { "type": "boolean", "required": false, "defaultValue": false }, "userId": { "type": "string", "required": false, "references": { "model": "user", "field": "id", "onDelete": "cascade" } }, "createdAt": { "type": "date" }, "updatedAt": { "type": "date" } } }, "oauthAccessToken": { "modelName": "oauthAccessToken", "fields": { "accessToken": { "type": "string", "unique": true }, "refreshToken": { "type": "string", "unique": true }, "accessTokenExpiresAt": { "type": "date" }, "refreshTokenExpiresAt": { "type": "date" }, "clientId": { "type": "string", "references": { "model": "oauthApplication", "field": "clientId", "onDelete": "cascade" } }, "userId": { "type": "string", "required": false, "references": { "model": "user", "field": "id", "onDelete": "cascade" } }, "scopes": { "type": "string" }, "createdAt": { "type": "date" }, "updatedAt": { "type": "date" } } }, "oauthConsent": { "modelName": "oauthConsent", "fields": { "clientId": { "type": "string", "references": { "model": "oauthApplication", "field": "clientId", "onDelete": "cascade" } }, "userId": { "type": "string", "references": { "model": "user", "field": "id", "onDelete": "cascade" } }, "scopes": { "type": "string" }, "createdAt": { "type": "date" }, "updatedAt": { "type": "date" }, "consentGiven": { "type": "boolean" } } } } } } ], "cookieCache": { "enabled": true, "maxAge": 300 }, "advanced": { "cookiePrefix": "idapp" }, "trustedOrigins": [ "https://idapp.appname.local", "https://clientapp.appname.local" ], "secret": "[REDACTED]", "url": "http://localhost:3001" } } } ``` ### Which area(s) are affected? (Select all that apply) Backend ### Auth config (if applicable) ```typescript ``` ### Additional context After some investigation, I noticed that the OauthApplications table in the database is empty. Even after running `npx @better-auth/cli generate` followed by `npx prisma db push`, the table is still empty. It seems as if the trustedClients: object in the oidcProvider's config should automatically add these records to the database, but it does not.
GiteaMirror added the nextjsbug labels 2026-03-13 09:24:14 -05:00
Author
Owner

@dosubot[bot] commented on GitHub (Oct 2, 2025):

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

This is expected behavior: trustedClients in the oidcProvider config are not automatically inserted into the oauthApplication table. They are used for runtime checks and consent skipping, but for token issuance, the client must exist in the database due to foreign key constraints. That's why you see the foreign key error when trying to sign in—the referenced clientId isn't present in the table.

To fix this, you need to manually insert your trusted client(s) into the oauthApplication table, matching the config fields (clientId, clientSecret, name, redirectURLs, type, etc). You can do this with a direct SQL insert, a Prisma create, or by enabling dynamic client registration and using the /oauth2/register endpoint. Once the client exists in the table, token generation will work and the error will disappear. This gap between docs and implementation is known and discussed in issue #4749.

Example Prisma code:

await prisma.oauthApplication.create({
  data: {
    clientId: "clientApp",
    clientSecret: "xxx",
    name: "Client App",
    type: "web",
    redirectURLs: "https://clientapp.appname.local/api/auth/oauth2/callback/idApp",
    disabled: false,
    metadata: JSON.stringify({}),
    createdAt: new Date(),
    updatedAt: new Date(),
  }
});

For more details and alternatives, see the docs and related discussion.

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 (Oct 2, 2025): <!-- Greeting --> Hi @pxtrickb! I'm [Dosu](https://go.dosu.dev/dosubot) and I’m helping the better-auth team. <!-- Answer --> This is expected behavior: trustedClients in the oidcProvider config are not automatically inserted into the `oauthApplication` table. They are used for runtime checks and consent skipping, but for token issuance, the client must exist in the database due to foreign key constraints. That's why you see the foreign key error when trying to sign in—the referenced clientId isn't present in the table. To fix this, you need to manually insert your trusted client(s) into the `oauthApplication` table, matching the config fields (`clientId`, `clientSecret`, `name`, `redirectURLs`, `type`, etc). You can do this with a direct SQL insert, a Prisma create, or by enabling dynamic client registration and using the `/oauth2/register` endpoint. Once the client exists in the table, token generation will work and the error will disappear. This gap between docs and implementation is known and discussed in [issue #4749](https://github.com/better-auth/better-auth/issues/4749#issuecomment-3307331347). Example Prisma code: ```ts await prisma.oauthApplication.create({ data: { clientId: "clientApp", clientSecret: "xxx", name: "Client App", type: "web", redirectURLs: "https://clientapp.appname.local/api/auth/oauth2/callback/idApp", disabled: false, metadata: JSON.stringify({}), createdAt: new Date(), updatedAt: new Date(), } }); ``` For more details and alternatives, see [the docs](https://github.com/better-auth/better-auth/blob/d1fe10962079c82920ea078a9847f65a59907b81/docs/content/docs/plugins/oidc-provider.mdx) and [related discussion](https://github.com/better-auth/better-auth/issues/4749#issuecomment-3307331347). <!-- 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/ae13efce-78cd-47dd-9c9d-83cb5ad6e9ce?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/ae13efce-78cd-47dd-9c9d-83cb5ad6e9ce?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/ae13efce-78cd-47dd-9c9d-83cb5ad6e9ce?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/ae13efce-78cd-47dd-9c9d-83cb5ad6e9ce?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/ae13efce-78cd-47dd-9c9d-83cb5ad6e9ce?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/ae13efce-78cd-47dd-9c9d-83cb5ad6e9ce?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/ae13efce-78cd-47dd-9c9d-83cb5ad6e9ce?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/5040)
Author
Owner

@bnssoftware commented on GitHub (Nov 11, 2025):

Trusted clients should be able to be added to the table and a skip consent option should be available.

@bnssoftware commented on GitHub (Nov 11, 2025): Trusted clients should be able to be added to the table and a skip consent option should be available.
Author
Owner

@dvanmali commented on GitHub (Dec 24, 2025):

Hi all, we released the new OAuth Provider Plugin which requires all clients to exist on the database and is cacheable with cachedTrustedClients. Feel free to let us know how it works :)

@dvanmali commented on GitHub (Dec 24, 2025): Hi all, we released the new [OAuth Provider Plugin](https://www.better-auth.com/docs/plugins/oauth-provider) which requires all clients to exist on the database and is cacheable with `cachedTrustedClients`. Feel free to let us know how it works :)
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#2065