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:
- 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. 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.
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_providerUI 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 withsso()and the realcreateObjectQLAdapterFactoryadapter over a real better-sqlite3 engine, authenticated with a real session, callingrunRegisterSsoProviderFromForm.Cause
packages/plugins/plugin-auth/src/register-sso-provider.ts:122-126sets the claim mapping unconditionally:But the pinned
@better-auth/sso@1.7.0-rc.2declares the mapping as a strict object with noidmember (dist/index.mjs:1462):idis never optional-extra —strictObjectrejects it outright, so the key the bridge always sends guarantees a 400. ThemapIdaction param atsys-sso-provider.object.ts:102("Map: User ID claim", defaultsub) feeds the same dead key.Why it went unnoticed
Two things line up:
auth-manager.ts:2548records the plugin surface as "verified against 1.6.20", but the dependency is now pinned at1.7.0-rc.2. The mapping schema is one of the things that moved across that bump.register-sso-provider.test.tscovers 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
idfrom the emitted mapping and re-home the "user ID claim" intent.subis the OIDC subject identifier and better-auth resolves it itself; if a configurable subject claim is genuinely wanted,extraFieldsis the member the strict schema leaves open. ThemapIdaction param should go or be re-pointed in the same change. A test on the OIDC bridge asserting the emitted body parses against the installedoidcMappingSchemawould 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/mainat 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.