SSO plugin incompatible with snake_case database schemas #2227

Closed
opened 2026-03-13 09:35:52 -05:00 by GiteaMirror · 1 comment
Owner

Originally created by @programad on GitHub (Oct 28, 2025).

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

  1. Define a Drizzle schema with snake_case columns (this is done by Drizzle):
export const sso_provider = pgTable('sso_provider', {
  id: uuid('id').primaryKey(),
  provider_id: text('provider_id').notNull(),
  user_id: uuid('user_id').notNull(),
  organization_id: uuid('organization_id'),
  oidc_config: text('oidc_config'),
  saml_config: text('saml_config'),
  created_at: timestamp('created_at').defaultNow(),
  updated_at: timestamp('updated_at').defaultNow(),
});
  1. Configure Better Auth with field mappings:
betterAuth({
  database: drizzleAdapter(client, {
    schema: { ssoProvider: sso_provider }
  }),
  ssoProvider: {
    modelName: 'sso_provider',
    fields: {
      providerId: 'provider_id',
      userId: 'user_id',
      organizationId: 'organization_id',
      oidcConfig: 'oidc_config',
    }
  },
  plugins: [sso()]
})
  1. Attempt SSO login via POST to /auth/sign-in/sso endpoint

Current vs. Expected behavior

Expected Behavior

The SSO plugin should respect the ssoProvider.fields mappings configured in the Better Auth options, translating camelCase field names to the actual snake_case database columns, similar to how core models (user, account, session) handle field mappings.

Actual Behavior

Error 1 (without field mappings):
[Better Auth ERROR] The field "providerId" does not exist in the schema for the model "ssoProvider".

Error 2 (with field mappings + forked plugin using snake_case):
Field provider_id not found in model ssoProvider

The plugin's adapter queries bypass the field mapping layer, querying with literal field names that don't match the database schema.

What version of Better Auth are you using?

1.3.33

System info

{
  "system": {
    "platform": "darwin",
    "arch": "arm64",
    "version": "Darwin Kernel Version 24.6.0: Mon Jul 14 11:30:30 PDT 2025; root:xnu-11417.140.69~1/RELEASE_ARM64_T6020",
    "release": "24.6.0",
    "cpuCount": 12,
    "cpuModel": "Apple M2 Max",
    "totalMemory": "32.00 GB",
    "freeMemory": "0.21 GB"
  },
  "node": {
    "version": "v22.19.0",
    "env": "development"
  },
  "packageManager": {
    "name": "npm",
    "version": "10.9.3"
  },
  "frameworks": [
    {
      "name": "react",
      "version": "^18.3.1"
    },
    {
      "name": "hono",
      "version": "^4.10.3"
    }
  ],
  "databases": null,
  "betterAuth": {
    "version": "^1.3.33",
    "config": null
  }
}

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

What We Tried:

  1. Field mappings in betterAuth() config - Plugin ignores them
  2. Forking the plugin locally - Changed all ADAPTER_FIELDS to snake_case, but Better Auth's validation layer expects camelCase
  3. Hybrid approach - Complex schema manipulation to provide both naming styles, still encountering validation errors

Real-World Impact:
Our multi-tenant SaaS has 50+ tables using snake_case consistently. We need enterprise SSO but cannot:

  • Migrate the entire database schema (breaking change for production)
  • Use the official plugin (naming incompatibility)
  • Maintain a fork long-term (maintenance burden)
  • Keep only the necessary SSO tables as camelCalse

Root Cause:
The plugin uses hardcoded field names in adapter queries:

provider = await ctx.context.adapter.findOne({
  model: 'ssoProvider',
  where: [{ field: 'providerId', value: providerId }]
});

Unlike core Better Auth models (user, account, session), the SSO plugin doesn't utilize the field mapping configuration defined in options.ssoProvider.fields.

Proposed Solution:

Option 1 - Plugin-Level Field Mappings:

sso({
  fieldMappings: {
    providerId: 'provider_id',
    userId: 'user_id',
    organizationId: 'organization_id',
    oidcConfig: 'oidc_config',
    samlConfig: 'saml_config',
    createdAt: 'created_at',
    updatedAt: 'updated_at'
  }
})

Option 2 - Automatic Field Mapping Inheritance:
Have the plugin automatically read from options.ssoProvider.fields mappings when constructing adapter queries.

Benefits:
Zero breaking changes - Defaults to camelCase
Consistent with core - Same pattern as user/account/session tables
Flexible - Works with any naming convention
No schema changes - Pure configuration fix

Current Workaround:
Maintaining a local fork with extensive modifications. Not sustainable for production.

Originally created by @programad on GitHub (Oct 28, 2025). ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce 1. Define a Drizzle schema with snake_case columns (this is done by Drizzle): ``` export const sso_provider = pgTable('sso_provider', { id: uuid('id').primaryKey(), provider_id: text('provider_id').notNull(), user_id: uuid('user_id').notNull(), organization_id: uuid('organization_id'), oidc_config: text('oidc_config'), saml_config: text('saml_config'), created_at: timestamp('created_at').defaultNow(), updated_at: timestamp('updated_at').defaultNow(), }); ``` 2. Configure Better Auth with field mappings: ``` betterAuth({ database: drizzleAdapter(client, { schema: { ssoProvider: sso_provider } }), ssoProvider: { modelName: 'sso_provider', fields: { providerId: 'provider_id', userId: 'user_id', organizationId: 'organization_id', oidcConfig: 'oidc_config', } }, plugins: [sso()] }) ``` 3. Attempt SSO login via POST to /auth/sign-in/sso endpoint ### Current vs. Expected behavior # Expected Behavior The SSO plugin should respect the ssoProvider.fields mappings configured in the Better Auth options, translating camelCase field names to the actual snake_case database columns, similar to how core models (user, account, session) handle field mappings. # Actual Behavior Error 1 (without field mappings): `[Better Auth ERROR] The field "providerId" does not exist in the schema for the model "ssoProvider".` Error 2 (with field mappings + forked plugin using snake_case): `Field provider_id not found in model ssoProvider` The plugin's adapter queries bypass the field mapping layer, querying with literal field names that don't match the database schema. ### What version of Better Auth are you using? 1.3.33 ### System info ```bash { "system": { "platform": "darwin", "arch": "arm64", "version": "Darwin Kernel Version 24.6.0: Mon Jul 14 11:30:30 PDT 2025; root:xnu-11417.140.69~1/RELEASE_ARM64_T6020", "release": "24.6.0", "cpuCount": 12, "cpuModel": "Apple M2 Max", "totalMemory": "32.00 GB", "freeMemory": "0.21 GB" }, "node": { "version": "v22.19.0", "env": "development" }, "packageManager": { "name": "npm", "version": "10.9.3" }, "frameworks": [ { "name": "react", "version": "^18.3.1" }, { "name": "hono", "version": "^4.10.3" } ], "databases": null, "betterAuth": { "version": "^1.3.33", "config": null } } ``` ### 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 What We Tried: 1. Field mappings in betterAuth() config - Plugin ignores them 2. Forking the plugin locally - Changed all ADAPTER_FIELDS to snake_case, but Better Auth's validation layer expects camelCase 3. Hybrid approach - Complex schema manipulation to provide both naming styles, still encountering validation errors Real-World Impact: Our multi-tenant SaaS has 50+ tables using snake_case consistently. We need enterprise SSO but cannot: - Migrate the entire database schema (breaking change for production) - Use the official plugin (naming incompatibility) - Maintain a fork long-term (maintenance burden) - Keep only the necessary SSO tables as camelCalse Root Cause: The plugin uses hardcoded field names in adapter queries: ``` provider = await ctx.context.adapter.findOne({ model: 'ssoProvider', where: [{ field: 'providerId', value: providerId }] }); ``` Unlike core Better Auth models (user, account, session), the SSO plugin doesn't utilize the field mapping configuration defined in options.ssoProvider.fields. Proposed Solution: Option 1 - Plugin-Level Field Mappings: ``` sso({ fieldMappings: { providerId: 'provider_id', userId: 'user_id', organizationId: 'organization_id', oidcConfig: 'oidc_config', samlConfig: 'saml_config', createdAt: 'created_at', updatedAt: 'updated_at' } }) ``` Option 2 - Automatic Field Mapping Inheritance: Have the plugin automatically read from options.ssoProvider.fields mappings when constructing adapter queries. Benefits: ✅ Zero breaking changes - Defaults to camelCase ✅ Consistent with core - Same pattern as user/account/session tables ✅ Flexible - Works with any naming convention ✅ No schema changes - Pure configuration fix Current Workaround: Maintaining a local fork with extensive modifications. Not sustainable for production.
GiteaMirror added the pluginbug labels 2026-03-13 09:35:53 -05:00
Author
Owner

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

You are correct: the SSO plugin currently does not respect field mappings for snake_case schemas. Unlike core models (user, account, session), the SSO plugin uses hardcoded camelCase field names in its adapter queries, which bypasses the field mapping logic and causes the errors you described. This is a known limitation and has been reported with other plugins as well, such as the username and phoneNumber plugins (example, example).

The core adapter system in Better Auth is designed to translate between logical (camelCase) and physical (snake_case) field names using the schema mappings, but the SSO plugin does not leverage this and instead queries with literal field names like "providerId" or "organizationId" (source). This means your proposed solutions—either plugin-level field mappings or automatic inheritance from the ssoProvider.fields config—are both technically feasible and would align the SSO plugin with the rest of the framework.

Until this is addressed upstream, the only workarounds are to maintain a fork or monkey-patch the plugin to rewrite field names before DB operations, as has been done for other plugins (example workaround). There is no built-in or officially recommended fix as of v1.3.33.

Your analysis and proposed solutions are spot-on and would resolve the issue without breaking changes.

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 28, 2025): <!-- Answer --> You are correct: the SSO plugin currently does not respect field mappings for snake_case schemas. Unlike core models (user, account, session), the SSO plugin uses hardcoded camelCase field names in its adapter queries, which bypasses the field mapping logic and causes the errors you described. This is a known limitation and has been reported with other plugins as well, such as the username and phoneNumber plugins ([example](https://github.com/better-auth/better-auth/issues/4066#issuecomment-3196533781), [example](https://github.com/better-auth/better-auth/issues/5157#issuecomment-3379372801)). The core adapter system in Better Auth is designed to translate between logical (camelCase) and physical (snake_case) field names using the schema mappings, but the SSO plugin does not leverage this and instead queries with literal field names like "providerId" or "organizationId" ([source](https://github.com/better-auth/better-auth/blob/c4bbb290d9c12ce6774e85d62243450470a0023b/packages/better-auth/src/plugins/sso/index.ts)). This means your proposed solutions—either plugin-level field mappings or automatic inheritance from the ssoProvider.fields config—are both technically feasible and would align the SSO plugin with the rest of the framework. Until this is addressed upstream, the only workarounds are to maintain a fork or monkey-patch the plugin to rewrite field names before DB operations, as has been done for other plugins ([example workaround](https://github.com/better-auth/better-auth/issues/4066#issuecomment-3196533781)). There is no built-in or officially recommended fix as of v1.3.33. Your analysis and proposed solutions are spot-on and would resolve the issue without breaking changes. <!-- 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/a658dd70-e96a-4364-aa39-b3cdb4368944?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/a658dd70-e96a-4364-aa39-b3cdb4368944?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/a658dd70-e96a-4364-aa39-b3cdb4368944?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/a658dd70-e96a-4364-aa39-b3cdb4368944?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/a658dd70-e96a-4364-aa39-b3cdb4368944?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/a658dd70-e96a-4364-aa39-b3cdb4368944?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/a658dd70-e96a-4364-aa39-b3cdb4368944?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/5649)
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#2227