feat(auth): @better-auth/sso scaffold — per-env external IdP (OIDC/SAML) RP#2322
Conversation
…ng party
ADR-0024 V1: the OPEN per-env SSO mechanism. Lets an environment federate
login to a customer's own OIDC/SAML IdP (Okta / Entra / Google), registered
at runtime in the env (cloud-free for self-host).
- platform-objects: new `sys_sso_provider` object mirroring @better-auth/sso
@1.6.20's BaseSSOProvider (issuer, oidc_config/saml_config JSON blobs,
user_id, provider_id, organization_id, domain); managedBy: better-auth;
mutations via /api/v1/auth/sso/{register,delete-provider}.
- plugin-auth: AUTH_SSO_PROVIDER_SCHEMA + buildSsoPluginSchema() (camelCase→
snake_case, per the buildOauthProviderPluginSchema template); auth-manager
wires sso({ schema }) gated by enabled.sso (OS_SSO_ENABLED, mirrors
OS_OIDC_PROVIDER_ENABLED).
- package.json: add @better-auth/sso ^1.6.20.
Follow-ups: provisionUser default-role, ssoClient() + admin UI (PR-2).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…er map @better-auth/sso (1.6.20) exposes NO `schema` option (verified: no mergeSchema, runtime never reads options.schema), so the per-plugin schema-remap that oauthProvider uses is unavailable. Drop the invalid `schema` arg + the dead buildSsoPluginSchema(); call sso() bare (gated off by OS_SSO_ENABLED). Add ssoProvider -> sys_sso_provider to AUTH_MODEL_TO_PROTOCOL. KNOWN-INCOMPLETE (documented inline): the ACTIVE factory adapter (createObjectQLAdapterFactory) passes the raw better-auth model name to the data engine and maps fields only from per-model declarations — neither of which the sso plugin provides. Finishing the integration needs objectql-adapter to resolve schema-less plugin model + camelCase field names, then full E2E. Compiles green (DTS ok for plugin-auth + platform-objects); feature off by default. + pnpm-lock.yaml (@better-auth/sso ^1.6.20). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📓 Docs Drift CheckThis PR changes 2 package(s): 11 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
…sso) Make createObjectQLAdapterFactory resolve the table name (resolveProtocolName) AND map camelCase<->snake fields for models remapped via AUTH_MODEL_TO_PROTOCOL — i.e. better-auth plugins like @better-auth/sso that expose no `schema` option. Scoped by `objectName !== model`, so core / schema-declared models are byte-for-byte unchanged. This makes the @better-auth/sso integration (sys_sso_provider) functional at the data layer. Verified: 29 unit tests pass — 3 new sso-bridging tests drive the REAL better-auth adapter wrapper (sso plugin loaded): insert maps ssoProvider + oidcConfig -> sys_sso_provider + oidc_config, reads map back to camelCase, where fields are snaked; the 26 existing core-path tests are unchanged. Remaining: full browser E2E (login via a live external OIDC IdP -> JIT user + sys_account.provider_id + default role). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Update — the adapter gap is now implemented + verified at the data layer.
29 unit tests pass — 3 new sso-bridging tests drive the real better-auth adapter wrapper (sso plugin loaded):
Compiles green (plugin-auth + platform-objects DTS). Remaining before "done": full browser E2E — |
…isions Without adding SysSsoProvider to authIdentityObjects (the manifest the Auth plugin provisions), the table is never created and the sso plugin's writes fail at runtime. Caught by the live E2E: with this, sys_sso_provider provisions with the correct snake columns. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
✅ Full live E2E passed — two federated objectos instancesRan a real two-node OIDC federation from this branch: OP = a framework objectos with
Surfaced + fixed by the E2E: One caveat: the OP consent screen is Net: the new code is verified end-to-end (boot → provision → register → initiate → federated login → JIT) in a live runtime, on top of the 29 passing unit tests. Follow-ups remain (provisionUser default-role; |
|
UI half landed → the login-page "Sign in with SSO" entry is objectstack-ai/objectui#2000. Together with this backend PR, the per-env external-IdP SSO flow is complete and browser-verified end-to-end (two federated objectos instances): login page → click "Sign in with SSO" → IdP login → consent → JIT federated session ( |
… (SSO login button) Pulls in objectui#2000 (the "Sign in with SSO" login-page entry) so the framework's bundled @objectstack/console ships the SSO UI alongside this PR's @better-auth/sso backend. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
What's here (builds green — DTS ok for
plugin-auth+platform-objects)sys_sso_providerobject (platform-objects/.../identity/) mirroring@better-auth/sso@1.6.20'sBaseSSOProvider(issuer, oidc_config/saml_config JSON blobs, user_id, provider_id, organization_id, domain);managedBy: 'better-auth'; mutations via/api/v1/auth/sso/{register,delete-provider}.plugin-authwiring:sso()plugin gated byenabled.sso(OS_SSO_ENABLED, mirrorsOS_OIDC_PROVIDER_ENABLED);ssoProvider → sys_sso_provideradded toAUTH_MODEL_TO_PROTOCOL.@better-auth/sso ^1.6.20(+ lockfile).The gap I uncovered (the real remaining work)
@better-auth/sso@1.6.20exposes noschemaoption — verified: nomergeSchema, and the runtime never readsoptions.schema(unlikeoauthProvider, which the framework remaps viabuildOauthProviderPluginSchema). So the sso plugin cannot declare its model/field names.The active adapter (
createObjectQLAdapterFactory) passes the raw better-auth model name to the data engine (dataEngine.insert(model, …)) and maps fields only from per-model declarations (coreuser/session/accountconfig or a pluginschemaoption). The sso plugin provides neither, so at runtime:ssoProviderwon't resolve tosys_sso_provider, andoidcConfig, …) won't map to snake columns.To finish (next PR): extend
objectql-adapterso the factory adapter resolves schema-less plugin model names (consultresolveProtocolName/AUTH_MODEL_TO_PROTOCOL) and maps camelCase→snake fields for them; updateobjectql-adapter.test.ts; then full local E2E (register an OIDC IdP → login → confirm JITsys_user+sys_account.provider_id+ default role).provisionUserdefault-role,ssoClient(), and the admin UI are also follow-ups.Open-core / refs
Per-env SSO mechanism is OPEN (framework; self-host needs it — ADR-0024 topology boundary). Refs: ADR-0024 (objectstack-ai/cloud#502), tracker objectstack-ai/cloud#500.
🤖 Generated with Claude Code