Skip to content

feat(scim): store a per-provider SCIM token - #2670

Open
xlgmokha wants to merge 3 commits into
masterfrom
xlgmokha/auth-1368a
Open

feat(scim): store a per-provider SCIM token#2670
xlgmokha wants to merge 3 commits into
masterfrom
xlgmokha/auth-1368a

Conversation

@xlgmokha

@xlgmokha xlgmokha commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

What kind of change does this PR introduce?

Feature. Adds sso_providers.scim_token_hash, a SHA-256 hex digest of the provider's SCIM token, with a partial unique index over the non-null values.

https://linear.app/supabase/issue/AUTH-1368/scim-users-get-by-id)

What is the current behavior?

The /scim/v2 router ships dark behind GOTRUE_EXPERIMENTAL_SCIM_ENABLED and serves the three discovery endpoints, but it has no authentication at all.

There is no way to associate a SCIM request with an SSO provider: sso_providers has no SCIM columns.

ServiceProviderConfig already advertises oauthbearertoken as its primary authentication scheme, with nothing behind it.

What is the new behavior?

sso_providers gains scim_token_hash, holding the SHA-256 hex digest of the provider's SCIM token, plus a partial unique index over the non-null values.
The plaintext token is never stored. Resolution is a single indexed equality on the digest of a secret, so there is no plaintext comparison and no timing-sensitive path.

Additional context

@xlgmokha xlgmokha self-assigned this Aug 3, 2026
@xlgmokha
xlgmokha marked this pull request as ready for review August 3, 2026 20:53
@xlgmokha
xlgmokha requested a review from a team as a code owner August 3, 2026 20:53
@xlgmokha
xlgmokha force-pushed the xlgmokha/auth-1368a branch from 5c30fe8 to 105842b Compare August 4, 2026 16:18
@xlgmokha
xlgmokha force-pushed the xlgmokha/auth-1368a branch from 105842b to 5c30fe8 Compare August 4, 2026 16:43
@xlgmokha
xlgmokha force-pushed the xlgmokha/auth-1368a branch from 5c30fe8 to 681e3de Compare August 4, 2026 16:46
@xlgmokha
xlgmokha force-pushed the xlgmokha/auth-1368a branch from 681e3de to 8d5ab8e Compare August 4, 2026 17:19
@xlgmokha
xlgmokha force-pushed the xlgmokha/auth-1368a branch from 8d5ab8e to aef7060 Compare August 4, 2026 17:27
@xlgmokha
xlgmokha force-pushed the xlgmokha/auth-1368a branch from aef7060 to 605a241 Compare August 4, 2026 19:52
@xlgmokha
xlgmokha force-pushed the xlgmokha/auth-1368a branch from 605a241 to 76e14c9 Compare August 4, 2026 20:05
@xlgmokha
xlgmokha force-pushed the xlgmokha/auth-1368a branch from 76e14c9 to e43f8da Compare August 6, 2026 15:34
@xlgmokha
xlgmokha force-pushed the xlgmokha/auth-1368a branch from e43f8da to d01a1ec Compare August 6, 2026 18:07
@xlgmokha
xlgmokha force-pushed the xlgmokha/auth-1368a branch 2 times, most recently from 0c91448 to 71e3963 Compare August 6, 2026 19:47
@xlgmokha
xlgmokha force-pushed the xlgmokha/auth-1368a branch from afcc9c2 to 77cc6af Compare August 10, 2026 15:55
@xlgmokha
xlgmokha force-pushed the xlgmokha/auth-1368a branch from 77cc6af to 223c123 Compare August 10, 2026 22:50
@xlgmokha
xlgmokha force-pushed the xlgmokha/auth-1368a branch from 223c123 to 210713b Compare August 12, 2026 15:29
Comment thread internal/models/sso.go Outdated
Comment thread internal/models/sso.go
}

func toSHA256(token string) string {
sum := sha256.Sum256([]byte(token))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Severity: LOW

SCIMTokenHash is a bare, unsalted SHA-256 digest, while UpdateSCIMToken accepts arbitrary token strings without an entropy requirement. A database reader can test candidate low-entropy or provider-chosen tokens offline, recover a bearer token, and use it against the SCIM provider lookup.
Helpful? Add 👍 / 👎

💡 Fix Suggestion

Suggestion: There are two complementary mitigations needed:

  1. Enforce minimum token entropy: Change UpdateSCIMToken to return an error and reject tokens shorter than 32 bytes (e.g., if len(token) < 32 { return errors.New("SCIM token must be at least 32 characters") }). This prevents low-entropy tokens from being stored and makes offline dictionary attacks impractical. Update all callers accordingly.

  2. Replace unsalted SHA-256 with bcrypt: Since golang.org/x/crypto is already a dependency, replace toSHA256 with bcrypt.GenerateFromPassword([]byte(token), bcrypt.DefaultCost) so each stored hash gets a unique, random salt. This makes offline brute-force exponentially harder. Note that bcrypt is non-deterministic, so the equality-based lookup in FindSSOProviderBySCIMToken (WHERE scim_token_hash = ?) must be replaced with a fetch-then-compare pattern: load all providers with a non-null scim_token_hash (SCIM provider counts are small) and use bcrypt.CompareHashAndPassword to find a match. Alternatively, keep a fast SHA-256 lookup index but store a bcrypt hash as the authoritative credential field and verify using bcrypt after the indexed lookup.

@xlgmokha
xlgmokha force-pushed the xlgmokha/auth-1368a branch from 210713b to e04c509 Compare August 12, 2026 16:57
@xlgmokha
xlgmokha force-pushed the xlgmokha/auth-1368a branch 2 times, most recently from 81135e6 to 32ecad6 Compare August 12, 2026 17:13
Base automatically changed from xlgmokha/auth-1362e to master August 12, 2026 22:30
@xlgmokha
xlgmokha force-pushed the xlgmokha/auth-1368a branch from aa54b70 to 289d59c Compare August 12, 2026 22:30
@xlgmokha
xlgmokha force-pushed the xlgmokha/auth-1368a branch from 289d59c to cd7a474 Compare August 12, 2026 22:31
Comment thread internal/models/sso.go Outdated
@xlgmokha
xlgmokha force-pushed the xlgmokha/auth-1368a branch from 54f3478 to c94ceb6 Compare August 12, 2026 23:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant