SCIM Plugin#
The @better-auth/scim package is a Better Auth plugin that implements RFC 7644 (SCIM 2.0) provisioning. It exposes a /scim/v2/ API surface that identity providers (Okta, Azure AD, etc.) can use to create, read, update, and delete users directly against a Better Auth deployment.
Package entry point: packages/scim/src/index.ts
All routes: packages/scim/src/routes.ts
Endpoints#
| Method | Path | Purpose |
|---|---|---|
POST | /scim/v2/Users | Provision a new user |
GET | /scim/v2/Users | List provisioned users |
GET | /scim/v2/Users/:userId | Get a single user |
PUT | /scim/v2/Users/:userId | Replace a user |
PATCH | /scim/v2/Users/:userId | Partial update |
DELETE | /scim/v2/Users/:userId | Deprovision a user |
GET | /scim/v2/ServiceProviderConfig | SCIM capability metadata |
GET | /scim/v2/Schemas | Supported schemas |
GET | /scim/v2/ResourceTypes | Supported resource types |
Registered capabilities: patch ✓, filter ✓, bulk ✗, sort ✗, changePassword ✗ .
All SCIM routes are authenticated via a Bearer token tied to a scimProvider row. Tokens are minted by POST /scim/generate-token (requires an active session and the admin or owner org role by default) . The scimProvider schema adds providerId, scimToken, and optionally organizationId and userId fields .
GET /scim/v2/Users — Known Issues (open as of 2026-07-14)#
This endpoint has three confirmed RFC 7644 compliance violations, reported in issue #10398.
1. Silent result truncation at 100 rows#
listSCIMUsers calls adapter.findMany on both the account and user models without passing a limit . The adapter factory applies a default limit of options.advanced?.database?.defaultFindManyLimit ?? 100 whenever no explicit limit is provided . On deployments with more than 100 SCIM-provisioned users, only the first 100 rows are returned.
2. Incorrect totalResults#
The response sets totalResults to users.length — the count of the already-truncated result set — not the true total . RFC 7644 §3.4.2 (REQUIRED field) states: "The value may be larger than the number of resources returned, such as when returning a single page of results."
3. Missing startIndex and count query parameters#
The query schema only accepts filter . The startIndex and count pagination parameters defined in RFC 7644 §3.4.2.4 are absent — passing them has no effect.
Real-world consequence: A SCIM client observes totalResults == Resources.length == itemsPerPage and correctly concludes it has fetched all results, then treats users beyond position 100 as deleted in the provisioning system .
Workaround (until fixed)#
Raise defaultFindManyLimit to prevent truncation. This does not fix the wrong totalResults or add real pagination:
export const auth = betterAuth({
advanced: {
database: {
defaultFindManyLimit: 10000,
},
},
});
Suggested fix#
- Add
startIndex(z.coerce.number().min(1).default(1)) andcount(z.coerce.number().min(0).optional()) tolistSCIMUsersQuerySchema. - Fetch all matching account IDs without a limit to obtain the true
totalResults. - Apply
countaslimitandstartIndex - 1asoffsetto the paginated user query. - Return the real account count as
totalResultsand the actual page size asitemsPerPage.
Other Implementation Notes#
-
Filtering: The
filterquery param onGET /scim/v2/Usersis parsed byscim-filters.ts. Only theeqoperator is implemented despite the regex matchingeq,ne,co,sw,ew,pr. The SCIM attributeuserNamemaps to the DBemailfield. -
Deactivation:
active: falsemaps to the admin plugin'sbannedfield. Settingactive: falsewithout the admin plugin throws a400. -
Organization scope: When a token carries an
organizationId,DELETE /scim/v2/Users/:idremoves the org membership and SCIM account link only — it does not delete the global user . -
Linking existing users: Auto-linking a pre-existing user by email alone is blocked unless
opts.linkExistingUsersis explicitly configured . -
Provider ownership:
providerOwnership.enabledisfalseby default pending a schema migration in a futurenext-minor release .
Key Source Files#
| File | Purpose |
|---|---|
packages/scim/src/index.ts | Plugin registration, schema definition |
packages/scim/src/routes.ts | All endpoint handlers |
packages/scim/src/scim-filters.ts | SCIM filter expression parser |
packages/core/src/db/adapter/factory.ts | Adapter factory with defaultFindManyLimit default |
| GitHub issue #10398 | Bug report: truncation & pagination |