Uh oh!
There was an error while loading. Please reload this page.
fix(plugin-auth): OIDC SSO registration works again — drop the retired oidcConfig.mapping.id - #8221
Conversation
…OIDC SSO registration (#8193) @better-auth/sso declares oidcConfig.mapping as a strict object. `id` was a real member in 1.6.20 and was honoured at login; the pinned 1.7.0-rc.2 retires it and reads the federated subject from the OIDC `sub` claim directly, so the key the bridge always sent made every OIDC registration answer 400. Emit { email, name } (the strict schema's required members), refuse a non-`sub` user-ID claim loudly rather than discarding it silently, and pin the path with tests that drive the real /sso/register endpoint of a real better-auth instance. Also corrects the stale "verified against 1.6.20" attestation in auth-manager.ts to what was actually re-measured against 1.7.0-rc.2. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PEVB6w7D7uCszR9Mw1BL73
…c-mapping-id-strictobject
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
📓 Docs Drift CheckThis PR changes 1 package(s): 8 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
⛔ 2 release-owned page(s) also reference the affected code. These are read-only:
|
Uh oh!
There was an error while loading. Please reload this page.
…ovider (objectstack-ai#8222) (objectstack-ai#8800) The OIDC subject claim is not configurable in @better-auth/sso@1.7.0-rc.2: oidcConfig.mapping is a z.strictObject with no `id` member, the subject is hard-wired to the `sub` claim, and extraFields is spread before `id` so it is not an escape hatch either. The form field's only accepted values were empty and the `sub` it already defaulted to; anything else has answered INVALID_REQUEST since objectstack-ai#8221. Removing it restores declared = enforced. Translation bundles regenerated via scripts/check-i18n-bundles.mjs --write, not hand-edited. The plugin-auth bridge guard is kept as belt-and-braces for non-form callers; only its doc comment changed. Claude-Session: https://claude.ai/code/session_01XeQRiAa7vYRVX5Fog7Zby8 Co-authored-by: Claude <noreply@anthropic.com>
Part of #8193
Part of, notFixes: this lands the wholeplugin-authhalf — OIDC SSOregistration works again — but the card also asks for the now-dead
mapIdaction param to go or be re-pointed, and that param lives in
packages/platform-objects(domain:metadata's package), which this card'sdispatch put out of scope. That half is filed separately and linked below.
Re-measured first
Reproduced on the real path before changing anything, against the installed
@better-auth/sso@1.7.0-rc.2(resolved frompackages/plugins/plugin-auth/node_modules), by driving the real/sso/registerendpoint of a realbetterAuth()instance carrying the realsso()plugin:400[body.oidcConfig.mapping] Unrecognized key: "id"iddropped401 Unauthorized— cleared the schema, stopped at the session gateidre-homed toextraFields401 Unauthorized— parses, but see belowThe 400 matches the reported symptom verbatim. The second row is the load-bearing
one:
@better-auth/ssovalidates the body before the endpoint's sessiongate, so reaching
401is positive evidence the emitted body parsed.idwas retired upstream, not movedRead out of the two package versions rather than from docs or memory:
oidcMappingSchemaas a plain, non-strictz.objectthat did carry
id, and honoured it when resolving the federated user:id: rawUserInfo[mapping.id || "sub"]/id: idToken[mapping.id || "sub"].So the bridge did not invent the key — it was correct against the version the
stale attestation names.
z.strictObject,and hard-wires the federated subject to the OIDC
subclaim(
id: readStringClaim(rawUserInfo, "sub")/id: idToken.sub), thencross-checks it (
id_token_subject_missing,id_token_userinfo_subject_mismatch).So there is no new home for the key, and dropping it is not merely what
makes the 400 go away.
extraFieldsis the one open member and it does parse —but it is spread before
idin the profile literal, so anextraFields.idis overwritten by
subbefore it is ever read. Re-homing there would have readas configured while doing nothing, which is worse than dropping it.
What changed
register-sso-provider.tsemitsmapping: { email, name }— the two membersthe strict schema requires. The email/name claim mappings the form collects
still work exactly as before.
registration asking for a non-
subuser-ID claim is now refused with aclear
INVALID_REQUESTinstead of being accepted and silently discarded.Empty, or
sub(the value the form suggests), registers as normal. This is thein-scope half of the card's "the
mapIdparam should go or be re-pointed":the param itself is not mine to touch, but honouring it silently is.
auth-manager.ts— the stale attestation. It claimed the plugin "accepts NOschemaoption (verified against 1.6.20 ... runtime never readsoptions.schema)". Re-checked against the pinned 1.7.0-rc.2: that claim is now
false —
SSOOptions.schema.ssoProviderexists and the runtime honoursmodelNameplus a per-fieldfieldNamemap. The comment now records what wasactually measured, and says plainly that only the mapping surface was
re-verified in depth. The adapter-level bridge itself is left alone (separate
change, filed below) rather than rewired silently.
The coverage hole
The OIDC bridge had zero tests — the reason a total breakage shipped. Five
cases added; the two load-bearing ones drive the real
/sso/registerof areal better-auth instance, so the emitted body is judged by the installed
package's own Zod schema rather than by a hand-copied restatement that would
drift silently on the next bump.
Predict-then-mutate ablation. Predicted, before running, that restoring only
the
idmember (keeping the new guard) turns 4 of 8 red: both real-path cases,the emitted-shape case, and the explicit-
subcase — with themapIdguard caseand all three SAML cases staying green. Measured: exactly that,
4 failed | 4 passed, all plainAssertionErrors, and the failure text is verbatim the 400this card reports.
Verification
pnpm --filter @objectstack/plugin-auth test— 45 files, 1080 tests passed(post-merge with
origin/main).pnpm --filter @objectstack/plugin-auth typecheck— clean.check:nul-bytes,check:test-source-alias,check:changeset-gate-self-tests,check:objectui-changeset,check:docs-audit-scope,check:error-code-casing,check:query-options-erasure,check:engine-double-contract,check-changeset-no-major— all green.check-objectui-pin-freshis red, and is red on an untouchedmaintoo(pre-existing, unrelated to this diff).
origin/mainwas merged before opening; it touchedauth-manager.tstoo andmerged cleanly with no conflict.
Generated by Claude Code