Relationships
#1821 SCIM 2.0 provisioning endpoint for swamp-club (Users + Groups, deprovision)
Opened by keeb · 8/25/2026
Problem
swamp-club has no SCIM 2.0 endpoint. rg -i scim across the repo returns nothing, and better-auth does not ship a SCIM plugin — the plugin export list in node_modules/better-auth/package.json:183-291 covers organization, sso, oidc-provider, device-authorization, jwt, bearer and admin, but nothing for SCIM. Any IdP configured to provision against swamp-club would 404 on every request and surface as a failed integration in the IdP admin console.
What exists today is just-in-time provisioning: provisionSsoMembership (lib/app/provision-sso-member.ts:37) upserts a member record on first SSO login, matched by email domain against the ssoProvider collection, tagged source: "sso".
Gap analysis against the four core SCIM operations
| SCIM operation | Today | Status |
|---|---|---|
| User create | JIT upsert at first SSO login | Equivalent outcome, but pull not push |
| User update | Nothing (see the overrideUserInfo issue) |
Gap |
| Group membership sync | Written only during the SSO login hook | Gap (see the idpGroups staleness issue) |
| Deprovision | Nothing automatic | Gap |
removeMember (lib/app/collective-commands.ts:380) exists only as a manual admin action behind an API route. Nothing removes a member when they leave the organization.
Why the existing data model fits
The pieces SCIM needs are already in place:
user— the directory, withidpGroupsdeclared as a custom field (lib/auth.ts:332)member— collective membership, carrying asourcefield that already distinguishes"sso"from manual invitesssoProvider— keyed bydomainwith anorganizationId, so SSO is already scoped per-collective
That last point matters: SCIM would inherit the same per-collective scoping SSO already uses, which is the correct granularity. And the existing source field gives a clean ownership boundary — SCIM-created members get source: "scim", which delimits what a SCIM sync is permitted to deprovision. Without that, a sync could delete manually-invited members.
Proposed scope
/scim/v2/Usersand/scim/v2/Groupswith GET, POST, PUT, PATCH, DELETE- A SCIM bearer credential per
ssoProviderrecord, verified in constant time - Okta-compatible behaviors: PATCH operations for group membership deltas,
filter=userName eq "...", pagination viastartIndex/count, soft-delete viaactive: falsemapped ontoremoveMember source: "scim"ownership rules governing what the sync may modify or remove
Landing SCIM Groups alone would substantially improve the existing chain: writes to idpGroups would flow through userinfo to serve idp-group: subjects on the next refresh tick, taking group staleness from "until next login" down to the refresh interval.
Open question
Self-hosted deployments running --auth-mode token never contact swamp-club, so a swamp-club SCIM integration does not serve them at all. If inbound provisioning for air-gapped installs is in scope, that is a separate integration against swamp serve rather than a variation of this one — worth deciding before design rather than after.
Context
Builds on the SSO and IdP group work in #1014, #1015, #1016, #1154, #1166, #1170, #1171, #1611.
Open
No activity in this phase yet.
keeb commented 8/25/2026, 2:34:14 PM
Two independently filed defects found during the same investigation, both of which SCIM would mitigate: #1819 (idpGroups only written by the SSO login hook, so group membership is stale until next login) and #1820 (overrideUserInfo never set, so profile fields are frozen at first login). #1819 in particular is the reason SCIM Groups alone would be worth landing early.
webframp commented 8/25/2026, 11:43:24 PM
👀
Sign in to post a ripple.