Skip to content

[bug] OIDC SSO registration is broken end-to-end: the bridge always sends oidcConfig.mapping.id, which @better-auth/sso@1.7.0-rc.2 rejects as an unrecognized key #8193

Description

@os-zhuang

Found (not fixed) while running the step-0 measurement for #8009. Out of that card's scope — filed separately, unassigned, for triage. Measured, not inferred.

Symptom

Every OIDC SSO provider registration through the sys_sso_providerregister_sso_provider UI action fails with HTTP 400:

{"status":400,"body":{"success":false,"error":{"code":"SSO_REGISTER_FAILED",
"message":"[body.oidcConfig.mapping] Unrecognized key: \"id\""}}}

That is real output from driving the real product path — a real betterAuth() instance with sso() and the real createObjectQLAdapterFactory adapter over a real better-sqlite3 engine, authenticated with a real session, calling runRegisterSsoProviderFromForm.

Cause

packages/plugins/plugin-auth/src/register-sso-provider.ts:122-126 sets the claim mapping unconditionally:

oidcConfig.mapping={id: str(body?.mapId)||'sub',email: str(body?.mapEmail)||'email',name: str(body?.mapName)||'name',};

But the pinned @better-auth/sso@1.7.0-rc.2 declares the mapping as a strict object with no id member (dist/index.mjs:1462):

constoidcMappingSchema=z.strictObject({email: z.string()...,emailVerified: z.string()...optional(),name: z.string()...,image: z.string()...optional(),extraFields: z.record(z.string(),z.any()).optional()}).optional();

id is never optional-extra — strictObject rejects it outright, so the key the bridge always sends guarantees a 400. The mapId action param at sys-sso-provider.object.ts:102 ("Map: User ID claim", default sub) feeds the same dead key.

Why it went unnoticed

Two things line up:

  1. The in-file comment at auth-manager.ts:2548 records the plugin surface as "verified against 1.6.20", but the dependency is now pinned at 1.7.0-rc.2. The mapping schema is one of the things that moved across that bump.
  2. register-sso-provider.test.ts covers only the SAML helper (runRegisterSamlProviderFromForm). The OIDC helper — the one with the bad key — has no test, and the SAML mapping schema is a different (also strict) object that the SAML bridge never populates.

So there is a coverage hole exactly where the regression landed.

Suggested shape (not decided here)

Drop id from the emitted mapping and re-home the "user ID claim" intent. sub is the OIDC subject identifier and better-auth resolves it itself; if a configurable subject claim is genuinely wanted, extraFields is the member the strict schema leaves open. The mapId action param should go or be re-pointed in the same change. A test on the OIDC bridge asserting the emitted body parses against the installed oidcMappingSchema would keep the next dependency bump honest.

Reachability / severity note

This is a broken feature, not a security issue: registration fails closed with a clear 400, and nothing is persisted. Grading is triage's call — recording it plainly per the file-plainly rule rather than pre-judging it small.

Evidence

Measured 2026-08-12 against origin/main at 8e7955b, @better-auth/sso@1.7.0-rc.2. Same harness and same run that produced the #8009 step-0 measurement (see that card's measurement comment).

Backlink: found during #8009.

Metadata

Metadata

Assignees

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions