Unable to generate access tokens for OIDC trusted providers as foreign keys point nowhere #1963

Closed
opened 2026-03-13 09:17:02 -05:00 by GiteaMirror · 6 comments
Owner

Originally created by @samuelh2005 on GitHub (Sep 18, 2025).

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

When trying to wire up an Auth.JS (Next Auth Beta) application to authenticate with Better Auth via OIDC Trusted Clients token creation fails as Better Auth attempts to store the token in a DB which fails forigien key checks.

  1. Create a Better Auth server with OIDC plugin in Next.JS and configure trusted clients (like in the config below)
  2. Configure a Postgres database (in my case Neon)
  3. Generate and run migrations with:
npx @better-auth/cli@latest generate
npx drizzle-kit generate
npx drizzle-kit migrate

If you are using the same auth config as I am you would get this result for auth-schema.ts

  1. Create a second Next.JS application and configure Auth.JS, example config:
import NextAuth from "next-auth"
 
export const { handlers, signIn, signOut, auth } = NextAuth({
    providers: [{
        id: "miners-online", // ID of the trusted client
        name: "Miners Online", // optional, used on the default login page as the button text.
        type: "oidc", 
        issuer: process.env.AUTH_ISSUER, // set to http://example.com/api/auth
        clientId: process.env.AUTH_CLIENT_ID, // same as the ID of the trusted client
        clientSecret: process.env.AUTH_CLIENT_SECRET, // same as `process.env.MINERS_ONLINE_HOME_CLIENT_SECRET` in auth config
    }],
})
  1. Create a button calling signIn from next-auth/react similar to:
              <Button className="w-fit" onClick={() => signIn("miners-online")}>
                Sign In
              </Button>
  1. When the user clicks the button there sign in is not successfull and the errors as shown in the logs appear

Current vs. Expected behavior

Expected behaviour:

I expect Better Auth does not produce errors during OIDC Trusted Client sign ins resulting in errors.

Current Behaviour:

The authentication process does not succseed and these logs are produced:

 GET /api/auth/.well-known/openid-configuration 200 in 1417ms
 GET /api/auth/.well-known/openid-configuration 200 in 13ms
 GET /api/auth/oauth2/authorize?response_type=code&client_id=miners-online-home&redirect_uri=http%3A%2F%2Flocalhost%3A4000%2Fapi%2Fauth%2Fcallback%2Fminers-online&code_challenge=REDACTED&code_challenge_method=S256&scope=openid+profile+email 302 in 548ms
 GET /api/auth/.well-known/openid-configuration 200 in 12ms
2025-09-18T12:18:36.972Z ERROR [Better Auth]: Error [Error: Failed query: insert into "oauth_access_token" ("id", "access_token", "refresh_token", "access_token_expires_at", "refresh_token_expires_at", "client_id", "user_id", "scopes", "created_at", "updated_at") values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) returning "id", "access_token", "refresh_token", "access_token_expires_at", "refresh_token_expires_at", "client_id", "user_id", "scopes", "created_at", "updated_at"
params: REDACTED,REDACTED,REDACTED,2025-09-18T13:18:36.000Z,2025-09-25T12:18:36.000Z,miners-online-home,REDACTED,openid profile email,2025-09-18T12:18:36.000Z,2025-09-18T12:18:36.000Z] {
  query: 'insert into "oauth_access_token" ("id", "access_token", "refresh_token", "access_token_expires_at", "refresh_token_expires_at", "client_id", "user_id", "scopes", "created_at", "updated_at") values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) returning "id", "access_token", "refresh_token", "access_token_expires_at", "refresh_token_expires_at", "client_id", "user_id", "scopes", "created_at", "updated_at"',
  params: [Array],
  [cause]: [Error [NeonDbError]: insert or update on table "oauth_access_token" violates foreign key constraint "oauth_access_token_client_id_oauth_application_client_id_fk"] {
    severity: 'ERROR',
    code: '23503',
    detail: 'Key (client_id)=(miners-online-home) is not present in table "oauth_application".',
    hint: undefined,
    position: undefined,
    internalPosition: undefined,
    internalQuery: undefined,
    where: undefined,
    schema: 'public',
    table: 'oauth_access_token',
    column: undefined,
    dataType: undefined,
    constraint: 'oauth_access_token_client_id_oauth_application_client_id_fk',
    file: 'ri_triggers.c',
    line: '2599',
    routine: 'ri_ReportViolation',
    sourceError: undefined
  }
}
# SERVER_ERROR:  [Error: Failed query: insert into "oauth_access_token" ("id", "access_token", "refresh_token", "access_token_expires_at", "refresh_token_expires_at", "client_id", "user_id", "scopes", "created_at", "updated_at") values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) returning "id", "access_token", "refresh_token", "access_token_expires_at", "refresh_token_expires_at", "client_id", "user_id", "scopes", "created_at", "updated_at"
params: REDACTED,2025-09-18T13:18:36.000Z,2025-09-25T12:18:36.000Z,miners-online-home,REDACTED,openid profile email,2025-09-18T12:18:36.000Z,2025-09-18T12:18:36.000Z] {
  query: 'insert into "oauth_access_token" ("id", "access_token", "refresh_token", "access_token_expires_at", "refresh_token_expires_at", "client_id", "user_id", "scopes", "created_at", "updated_at") values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) returning "id", "access_token", "refresh_token", "access_token_expires_at", "refresh_token_expires_at", "client_id", "user_id", "scopes", "created_at", "updated_at"',
  params: [Array],
  [cause]: [Error [NeonDbError]: insert or update on table "oauth_access_token" violates foreign key constraint "oauth_access_token_client_id_oauth_application_client_id_fk"] {
    severity: 'ERROR',
    code: '23503',
    detail: 'Key (client_id)=(miners-online-home) is not present in table "oauth_application".',
    hint: undefined,
    position: undefined,
    internalPosition: undefined,
    internalQuery: undefined,
    where: undefined,
    schema: 'public',
    table: 'oauth_access_token',
    column: undefined,
    dataType: undefined,
    constraint: 'oauth_access_token_client_id_oauth_application_client_id_fk',
    file: 'ri_triggers.c',
    line: '2599',
    routine: 'ri_ReportViolation',
    sourceError: undefined
  }
}
 POST /api/auth/oauth2/token 500 in 353ms

What version of Better Auth are you using?

1.3.11

System info

{
  "system": {
    "platform": "win32",
    "arch": "x64",
    "version": "Windows 11 Pro",
    "release": "10.0.26100",
    "cpuCount": 24,
    "cpuModel": "AMD Ryzen 9 7900X 12-Core Processor            ",
    "totalMemory": "31.16 GB",
    "freeMemory": "17.16 GB"
  },
  "node": {
    "version": "v22.14.0",
    "env": "development"
  },
  "packageManager": {
    "name": "npm",
    "version": "10.9.2"
  },
  "frameworks": [
    {
      "name": "next",
      "version": "15.5.2"
    },
    {
      "name": "react",
      "version": "19.1.0"
    }
  ],
  "databases": [
    {
      "name": "drizzle",
      "version": "^0.44.5"
    },
    {
      "name": "@neondatabase/serverless",
      "version": "^1.0.1"
    }
  ],
  "betterAuth": {
    "version": "^1.3.11",
    "config": {
      "socialProviders": {
        "github": {
          "clientId": "[REDACTED]",
          "clientSecret": "[REDACTED]"
        }
      },
      "emailAndPassword": {
        "enabled": true
      },
      "plugins": [
        {
          "name": "username",
          "config": {
            "id": "username",
            "endpoints": {},
            "schema": {
              "user": {
                "fields": {
                  "username": {
                    "type": "string",
                    "required": false,
                    "sortable": true,
                    "unique": true,
                    "returned": true,
                    "transform": {}
                  },
                  "displayUsername": {
                    "type": "string",
                    "required": false,
                    "transform": {}
                  }
                }
              }
            },
            "hooks": {
              "before": [
                {},
                {}
              ]
            },
            "$ERROR_CODES": {
              "INVALID_USERNAME_OR_PASSWORD": "[REDACTED]",
              "EMAIL_NOT_VERIFIED": "Email not verified",
              "UNEXPECTED_ERROR": "Unexpected error",
              "USERNAME_IS_ALREADY_TAKEN": "Username is already taken. Please try another.",
              "USERNAME_TOO_SHORT": "Username is too short",
              "USERNAME_TOO_LONG": "Username is too long",
              "INVALID_USERNAME": "Username is invalid",
              "INVALID_DISPLAY_USERNAME": "Display username is invalid"
            }
          }
        },
        {
          "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"
                  }
                }
              }
            }
          }
        }
      ]
    }
  }
}

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

Backend

Auth config (if applicable)

import { betterAuth } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { drizzle } from 'drizzle-orm/neon-http';

import { username, oidcProvider  } from "better-auth/plugins"

let schema: Record<string, unknown>;

try {
  // Dynamic import
  schema = await import("@/auth-schema");
} catch (_) {
  console.warn("@/auth-schema not found, using default schema");
  schema = {}; // fallback or default schema
}

const db = drizzle(process.env.DATABASE_URL!);

export const auth = betterAuth({
  socialProviders: {
    github: { 
      clientId: process.env.GITHUB_CLIENT_ID as string, 
      clientSecret: process.env.GITHUB_CLIENT_SECRET as string, 
    }, 
  },
  emailAndPassword: {
    enabled: true,
  },
  database: drizzleAdapter(db, {
    provider: "pg",
    schema: schema
  }), 
  plugins: [ 
    username(),
    oidcProvider({ // used to host an OIDC provider not third party login
      loginPage: "/auth/sign-in", // path to the login page
      trustedClients: [
        {
          clientId: "miners-online-home",
          clientSecret: process.env.MINERS_ONLINE_HOME_CLIENT_SECRET as string,
          name: "Miners Online Home",
          type: "web",
          redirectURLs: [
            "http://localhost:4000/api/auth/callback/miners-online",
            "https://minersonline.uk/api/auth/callback/miners-online"
          ],
          disabled: false,
          skipConsent: true,
          metadata: {}
        }
      ],
      metadata: {
        issuer: `${process.env.NEXT_PUBLIC_BASE_URL}/api/auth`
      }
    })
  ] 
});

Additional context

No response

Originally created by @samuelh2005 on GitHub (Sep 18, 2025). ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce When trying to wire up an Auth.JS (Next Auth Beta) application to authenticate with Better Auth via OIDC Trusted Clients token creation fails as Better Auth attempts to store the token in a DB which fails forigien key checks. 1. Create a Better Auth server with OIDC plugin in Next.JS and configure trusted clients (like in the config below) 2. Configure a Postgres database (in my case Neon) 3. Generate and run migrations with: ``` npx @better-auth/cli@latest generate npx drizzle-kit generate npx drizzle-kit migrate ``` If you are using the same auth config as I am you would get [this result for auth-schema.ts](https://github.com/miners-online/authserver/blob/4f2742dda15141797156ae3d8076aecb3c8002ad/auth-schema.ts) 4. Create a second Next.JS application and configure Auth.JS, example config: ```ts import NextAuth from "next-auth" export const { handlers, signIn, signOut, auth } = NextAuth({ providers: [{ id: "miners-online", // ID of the trusted client name: "Miners Online", // optional, used on the default login page as the button text. type: "oidc", issuer: process.env.AUTH_ISSUER, // set to http://example.com/api/auth clientId: process.env.AUTH_CLIENT_ID, // same as the ID of the trusted client clientSecret: process.env.AUTH_CLIENT_SECRET, // same as `process.env.MINERS_ONLINE_HOME_CLIENT_SECRET` in auth config }], }) ``` 5. Create a button calling `signIn` from `next-auth/react` similar to: ```tsx <Button className="w-fit" onClick={() => signIn("miners-online")}> Sign In </Button> ``` 6. When the user clicks the button there sign in is not successfull and the errors as shown in the logs appear ### Current vs. Expected behavior Expected behaviour: I expect Better Auth does not produce errors during OIDC Trusted Client sign ins resulting in errors. Current Behaviour: The authentication process does not succseed and these logs are produced: ``` GET /api/auth/.well-known/openid-configuration 200 in 1417ms GET /api/auth/.well-known/openid-configuration 200 in 13ms GET /api/auth/oauth2/authorize?response_type=code&client_id=miners-online-home&redirect_uri=http%3A%2F%2Flocalhost%3A4000%2Fapi%2Fauth%2Fcallback%2Fminers-online&code_challenge=REDACTED&code_challenge_method=S256&scope=openid+profile+email 302 in 548ms GET /api/auth/.well-known/openid-configuration 200 in 12ms 2025-09-18T12:18:36.972Z ERROR [Better Auth]: Error [Error: Failed query: insert into "oauth_access_token" ("id", "access_token", "refresh_token", "access_token_expires_at", "refresh_token_expires_at", "client_id", "user_id", "scopes", "created_at", "updated_at") values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) returning "id", "access_token", "refresh_token", "access_token_expires_at", "refresh_token_expires_at", "client_id", "user_id", "scopes", "created_at", "updated_at" params: REDACTED,REDACTED,REDACTED,2025-09-18T13:18:36.000Z,2025-09-25T12:18:36.000Z,miners-online-home,REDACTED,openid profile email,2025-09-18T12:18:36.000Z,2025-09-18T12:18:36.000Z] { query: 'insert into "oauth_access_token" ("id", "access_token", "refresh_token", "access_token_expires_at", "refresh_token_expires_at", "client_id", "user_id", "scopes", "created_at", "updated_at") values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) returning "id", "access_token", "refresh_token", "access_token_expires_at", "refresh_token_expires_at", "client_id", "user_id", "scopes", "created_at", "updated_at"', params: [Array], [cause]: [Error [NeonDbError]: insert or update on table "oauth_access_token" violates foreign key constraint "oauth_access_token_client_id_oauth_application_client_id_fk"] { severity: 'ERROR', code: '23503', detail: 'Key (client_id)=(miners-online-home) is not present in table "oauth_application".', hint: undefined, position: undefined, internalPosition: undefined, internalQuery: undefined, where: undefined, schema: 'public', table: 'oauth_access_token', column: undefined, dataType: undefined, constraint: 'oauth_access_token_client_id_oauth_application_client_id_fk', file: 'ri_triggers.c', line: '2599', routine: 'ri_ReportViolation', sourceError: undefined } } # SERVER_ERROR: [Error: Failed query: insert into "oauth_access_token" ("id", "access_token", "refresh_token", "access_token_expires_at", "refresh_token_expires_at", "client_id", "user_id", "scopes", "created_at", "updated_at") values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) returning "id", "access_token", "refresh_token", "access_token_expires_at", "refresh_token_expires_at", "client_id", "user_id", "scopes", "created_at", "updated_at" params: REDACTED,2025-09-18T13:18:36.000Z,2025-09-25T12:18:36.000Z,miners-online-home,REDACTED,openid profile email,2025-09-18T12:18:36.000Z,2025-09-18T12:18:36.000Z] { query: 'insert into "oauth_access_token" ("id", "access_token", "refresh_token", "access_token_expires_at", "refresh_token_expires_at", "client_id", "user_id", "scopes", "created_at", "updated_at") values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) returning "id", "access_token", "refresh_token", "access_token_expires_at", "refresh_token_expires_at", "client_id", "user_id", "scopes", "created_at", "updated_at"', params: [Array], [cause]: [Error [NeonDbError]: insert or update on table "oauth_access_token" violates foreign key constraint "oauth_access_token_client_id_oauth_application_client_id_fk"] { severity: 'ERROR', code: '23503', detail: 'Key (client_id)=(miners-online-home) is not present in table "oauth_application".', hint: undefined, position: undefined, internalPosition: undefined, internalQuery: undefined, where: undefined, schema: 'public', table: 'oauth_access_token', column: undefined, dataType: undefined, constraint: 'oauth_access_token_client_id_oauth_application_client_id_fk', file: 'ri_triggers.c', line: '2599', routine: 'ri_ReportViolation', sourceError: undefined } } POST /api/auth/oauth2/token 500 in 353ms ``` ### What version of Better Auth are you using? 1.3.11 ### System info ```bash { "system": { "platform": "win32", "arch": "x64", "version": "Windows 11 Pro", "release": "10.0.26100", "cpuCount": 24, "cpuModel": "AMD Ryzen 9 7900X 12-Core Processor ", "totalMemory": "31.16 GB", "freeMemory": "17.16 GB" }, "node": { "version": "v22.14.0", "env": "development" }, "packageManager": { "name": "npm", "version": "10.9.2" }, "frameworks": [ { "name": "next", "version": "15.5.2" }, { "name": "react", "version": "19.1.0" } ], "databases": [ { "name": "drizzle", "version": "^0.44.5" }, { "name": "@neondatabase/serverless", "version": "^1.0.1" } ], "betterAuth": { "version": "^1.3.11", "config": { "socialProviders": { "github": { "clientId": "[REDACTED]", "clientSecret": "[REDACTED]" } }, "emailAndPassword": { "enabled": true }, "plugins": [ { "name": "username", "config": { "id": "username", "endpoints": {}, "schema": { "user": { "fields": { "username": { "type": "string", "required": false, "sortable": true, "unique": true, "returned": true, "transform": {} }, "displayUsername": { "type": "string", "required": false, "transform": {} } } } }, "hooks": { "before": [ {}, {} ] }, "$ERROR_CODES": { "INVALID_USERNAME_OR_PASSWORD": "[REDACTED]", "EMAIL_NOT_VERIFIED": "Email not verified", "UNEXPECTED_ERROR": "Unexpected error", "USERNAME_IS_ALREADY_TAKEN": "Username is already taken. Please try another.", "USERNAME_TOO_SHORT": "Username is too short", "USERNAME_TOO_LONG": "Username is too long", "INVALID_USERNAME": "Username is invalid", "INVALID_DISPLAY_USERNAME": "Display username is invalid" } } }, { "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" } } } } } } ] } } } ``` ### Which area(s) are affected? (Select all that apply) Backend ### Auth config (if applicable) ```typescript import { betterAuth } from "better-auth"; import { drizzleAdapter } from "better-auth/adapters/drizzle"; import { drizzle } from 'drizzle-orm/neon-http'; import { username, oidcProvider } from "better-auth/plugins" let schema: Record<string, unknown>; try { // Dynamic import schema = await import("@/auth-schema"); } catch (_) { console.warn("@/auth-schema not found, using default schema"); schema = {}; // fallback or default schema } const db = drizzle(process.env.DATABASE_URL!); export const auth = betterAuth({ socialProviders: { github: { clientId: process.env.GITHUB_CLIENT_ID as string, clientSecret: process.env.GITHUB_CLIENT_SECRET as string, }, }, emailAndPassword: { enabled: true, }, database: drizzleAdapter(db, { provider: "pg", schema: schema }), plugins: [ username(), oidcProvider({ // used to host an OIDC provider not third party login loginPage: "/auth/sign-in", // path to the login page trustedClients: [ { clientId: "miners-online-home", clientSecret: process.env.MINERS_ONLINE_HOME_CLIENT_SECRET as string, name: "Miners Online Home", type: "web", redirectURLs: [ "http://localhost:4000/api/auth/callback/miners-online", "https://minersonline.uk/api/auth/callback/miners-online" ], disabled: false, skipConsent: true, metadata: {} } ], metadata: { issuer: `${process.env.NEXT_PUBLIC_BASE_URL}/api/auth` } }) ] }); ``` ### Additional context _No response_
GiteaMirror added the nextjsbug labels 2026-03-13 09:17:02 -05:00
Author
Owner

@dosubot[bot] commented on GitHub (Sep 18, 2025):

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

The error happens because trusted clients defined in the trustedClients array are not automatically inserted into the oauth_application table during setup or migration. When Better Auth tries to issue an access token, it enforces a foreign key constraint: the clientId must exist in oauth_application. Since migrations only create tables and fields, not data, you need to manually insert your trusted client(s) into the oauth_application table for the token flow to work without errors (docs, code).

You can fix this by either:

  • Manually inserting the trusted client into the oauth_application table (matching the config fields: clientId, clientSecret, name, redirectURLs, type, etc).
  • Or, if you want to automate this, use the /oauth2/register endpoint (enable dynamic client registration if needed) to create the client in the DB (docs).

Once the client exists in the table, token generation will succeed and the foreign key error will disappear. This is a known gap in the current workflow—trusted clients in config are for runtime checks and consent skipping, but DB seeding is still required for referential integrity.

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 (Sep 18, 2025): <!-- Greeting --> Hi @ajh123! I'm [Dosu](https://go.dosu.dev/dosubot) and I’m helping the better-auth team. <!-- Answer --> The error happens because trusted clients defined in the `trustedClients` array are not automatically inserted into the `oauth_application` table during setup or migration. When Better Auth tries to issue an access token, it enforces a foreign key constraint: the `clientId` must exist in `oauth_application`. Since migrations only create tables and fields, not data, you need to manually insert your trusted client(s) into the `oauth_application` table for the token flow to work without errors ([docs](https://github.com/better-auth/better-auth/blob/d1fe10962079c82920ea078a9847f65a59907b81/docs/content/docs/plugins/oidc-provider.mdx), [code](https://github.com/better-auth/better-auth/blob/d1fe10962079c82920ea078a9847f65a59907b81/packages/better-auth/src/plugins/oidc-provider/index.ts)). You can fix this by either: - Manually inserting the trusted client into the `oauth_application` table (matching the config fields: `clientId`, `clientSecret`, `name`, `redirectURLs`, `type`, etc). - Or, if you want to automate this, use the `/oauth2/register` endpoint (enable dynamic client registration if needed) to create the client in the DB ([docs](https://github.com/better-auth/better-auth/blob/d1fe10962079c82920ea078a9847f65a59907b81/docs/content/docs/plugins/oidc-provider.mdx)). Once the client exists in the table, token generation will succeed and the foreign key error will disappear. This is a known gap in the current workflow—trusted clients in config are for runtime checks and consent skipping, but DB seeding is still required for referential integrity. <!-- 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/8d038b61-da15-41cf-8737-ef8b77b1e9aa?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/8d038b61-da15-41cf-8737-ef8b77b1e9aa?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/8d038b61-da15-41cf-8737-ef8b77b1e9aa?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/8d038b61-da15-41cf-8737-ef8b77b1e9aa?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/8d038b61-da15-41cf-8737-ef8b77b1e9aa?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/8d038b61-da15-41cf-8737-ef8b77b1e9aa?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/8d038b61-da15-41cf-8737-ef8b77b1e9aa?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/4749)
Author
Owner

@samuelh2005 commented on GitHub (Sep 18, 2025):

@dosu

The very documentation you cited from d1fe109620/docs/content/docs/plugins/oidc-provider.mdx literaly says:

For first-party applications and internal services, you can configure trusted clients directly in your OIDC provider configuration. Trusted clients bypass database lookups for better performance and can optionally skip consent screens for improved user experience.

This means adding trusted clients into the database goes against recommended design as "Trusted clients bypass database lookups" (or supposed to).

@samuelh2005 commented on GitHub (Sep 18, 2025): @dosu The very documentation you cited from https://github.com/better-auth/better-auth/blob/d1fe10962079c82920ea078a9847f65a59907b81/docs/content/docs/plugins/oidc-provider.mdx literaly says: > For first-party applications and internal services, you can configure trusted clients directly in your OIDC provider configuration. Trusted clients bypass database lookups for better performance and can optionally skip consent screens for improved user experience. This means adding trusted clients into the database goes against recommended design as "Trusted clients bypass database lookups" (or supposed to).
Author
Owner

@dosubot[bot] commented on GitHub (Sep 18, 2025):

You're right—the documentation says trusted clients bypass database lookups for performance and consent purposes. However, the current implementation only skips DB lookups for authorization and consent, not for token storage. When an access token is issued, the code always inserts a row into the oauth_access_token table with a clientId that must exist in oauth_application due to a foreign key constraint. There’s no logic to skip this for trusted clients, so the DB still needs a row for the client—even if it’s trusted—otherwise you get the FK error you’re seeing.

So, while trusted clients do bypass some runtime DB checks, the schema enforces referential integrity for all tokens, which means you still have to seed the DB with trusted clients for token issuance to work. This is a gap between the docs and the actual behavior right now (see code, schema).

Until the implementation changes, the only workaround is to insert trusted clients into the DB (manually or via /oauth2/register).

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 (Sep 18, 2025): <!-- Answer --> You're right—the documentation says trusted clients bypass database lookups for performance and consent purposes. However, the current implementation only skips DB lookups for authorization and consent, not for token storage. When an access token is issued, the code always inserts a row into the `oauth_access_token` table with a `clientId` that must exist in `oauth_application` due to a foreign key constraint. There’s no logic to skip this for trusted clients, so the DB still needs a row for the client—even if it’s trusted—otherwise you get the FK error you’re seeing. So, while trusted clients do bypass some runtime DB checks, the schema enforces referential integrity for all tokens, which means you still have to seed the DB with trusted clients for token issuance to work. This is a gap between the docs and the actual behavior right now ([see code](https://github.com/better-auth/better-auth/blob/d1fe10962079c82920ea078a9847f65a59907b81/packages/better-auth/src/plugins/oidc-provider/index.ts), [schema](https://github.com/better-auth/better-auth/blob/d1fe10962079c82920ea078a9847f65a59907b81/packages/better-auth/src/plugins/oidc-provider/schema.ts)). Until the implementation changes, the only workaround is to insert trusted clients into the DB (manually or via `/oauth2/register`). <!-- 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/a23657d2-4815-4489-b479-9e3cc0358e35?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/a23657d2-4815-4489-b479-9e3cc0358e35?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/a23657d2-4815-4489-b479-9e3cc0358e35?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/a23657d2-4815-4489-b479-9e3cc0358e35?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/a23657d2-4815-4489-b479-9e3cc0358e35?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/a23657d2-4815-4489-b479-9e3cc0358e35?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/a23657d2-4815-4489-b479-9e3cc0358e35?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/4749)
Author
Owner

@samuelh2005 commented on GitHub (Sep 18, 2025):

@dosu

Prehaps it is not an implementation issue but rather incorrect / missleading documentation?

@samuelh2005 commented on GitHub (Sep 18, 2025): @dosu Prehaps it is not an implementation issue but rather incorrect / missleading documentation?
Author
Owner

@dosubot[bot] commented on GitHub (Sep 18, 2025):

You're correct—this was a documentation issue. The docs previously implied that trusted clients configured in code would fully bypass database requirements, but in reality, foreign key constraints for token issuance still require those clients to exist in the database. This mismatch was recently addressed: the schema and documentation were updated to explicitly state that clientId in access tokens and consents must reference an existing row in oauthApplication, enforcing referential integrity and cascade deletes. So, trusted clients do skip some runtime DB lookups, but for token storage, they must be present in the DB for things to work as expected (see merged PR #4264 and doc update).

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 (Sep 18, 2025): <!-- Answer --> You're correct—this was a documentation issue. The docs previously implied that trusted clients configured in code would fully bypass database requirements, but in reality, foreign key constraints for token issuance still require those clients to exist in the database. This mismatch was recently addressed: the schema and documentation were updated to explicitly state that `clientId` in access tokens and consents must reference an existing row in `oauthApplication`, enforcing referential integrity and cascade deletes. So, trusted clients do skip some runtime DB lookups, but for token storage, they must be present in the DB for things to work as expected ([see merged PR #4264 and doc update](https://github.com/better-auth/better-auth/pull/4264)). <!-- 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/3574a175-4c1d-4bbe-bff8-32c585c98d86?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/3574a175-4c1d-4bbe-bff8-32c585c98d86?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/3574a175-4c1d-4bbe-bff8-32c585c98d86?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/3574a175-4c1d-4bbe-bff8-32c585c98d86?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/3574a175-4c1d-4bbe-bff8-32c585c98d86?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/3574a175-4c1d-4bbe-bff8-32c585c98d86?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/3574a175-4c1d-4bbe-bff8-32c585c98d86?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/4749)
Author
Owner

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

Hi all, we released the new OAuth Provider Plugin which requires all clients to be defined on the database, trusted providers are now cacheable via 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 be defined on the database, trusted providers are now cacheable via `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#1963