feat(scim): store a per-provider SCIM token - #2670
Conversation
5c30fe8 to
105842b
Compare
105842b to
5c30fe8
Compare
5c30fe8 to
681e3de
Compare
681e3de to
8d5ab8e
Compare
8d5ab8e to
aef7060
Compare
aef7060 to
605a241
Compare
605a241 to
76e14c9
Compare
76e14c9 to
e43f8da
Compare
e43f8da to
d01a1ec
Compare
0c91448 to
71e3963
Compare
afcc9c2 to
77cc6af
Compare
77cc6af to
223c123
Compare
223c123 to
210713b
Compare
| } | ||
|
|
||
| func toSHA256(token string) string { | ||
| sum := sha256.Sum256([]byte(token)) |
There was a problem hiding this comment.
⚪ 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:
-
Enforce minimum token entropy: Change
UpdateSCIMTokento return anerrorand 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. -
Replace unsalted SHA-256 with bcrypt: Since
golang.org/x/cryptois already a dependency, replacetoSHA256withbcrypt.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 inFindSSOProviderBySCIMToken(WHERE scim_token_hash = ?) must be replaced with a fetch-then-compare pattern: load all providers with a non-nullscim_token_hash(SCIM provider counts are small) and usebcrypt.CompareHashAndPasswordto 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.
210713b to
e04c509
Compare
81135e6 to
32ecad6
Compare
aa54b70 to
289d59c
Compare
289d59c to
cd7a474
Compare
54f3478 to
c94ceb6
Compare
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/v2router ships dark behindGOTRUE_EXPERIMENTAL_SCIM_ENABLEDand 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_providershas no SCIM columns.ServiceProviderConfigalready advertisesoauthbearertokenas its primary authentication scheme, with nothing behind it.What is the new behavior?
sso_providersgainsscim_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