You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Idea: opt-in TrustContract for OIDC provider configuration
#4627
Idea: opt-in TrustContract for OIDC provider configuration
opening this as a Discussion (not a formal RFC issue) to float the shape and gather feedback before committing to a scope. the companion reliability fix for the error-flattening side of this is already in flight at #4626; this is the orthogonal "trust shape" piece.
one-line
add an optional *TrustContract pointer on oidc.ProviderConfig that lets operators declare their policy for email_verified, acr, amr per provider. nil pointer = current behavior exactly. non-nil = declarative gate before newIdentityFromClaims.
why
Kratos already defends against nOAuth-class account linking through three implicit layers (OIDCUniqueID credential indexing, password.identifier uniqueness, duplicate-flow password gate). the defense works — but it is implicit, scattered across identity/credentials_oidc.go:98, the default identity schema, and selfservice/strategy/oidc/strategy.go:702-731. operators who need to prove to auditors "we're not vulnerable to nOAuth" today walk reviewers through three code locations. a declarative per-provider contract would make that single-artifact.
three in-tree providers also silently set EmailVerified: true on claims the provider does not actually verify (provider_facebook.go:120-122, provider_slack.go:87, provider_discord.go:98), and no consumer code reads the field — so the lie is harmless today but would be surprising to anyone who later tries to add a gate.
#4581 showed the generic "any error becomes 401" pattern on the reliability side; the same code area has the orthogonal "any claim becomes trusted" pattern on the trust side. this discussion proposes the declarative fix for the second one.
sketch
selfservice:
methods:
oidc:
config:
providers:
- id: googleprovider: googletrust_contract:
verified_email_policy: require # google always mints the claimacceptable_amr: ["mfa", "hwk"] # admins must MFA
- id: facebookprovider: facebook# no trust_contract → current behavior preserved
typeTrustContractstruct {
VerifiedEmailPolicyVerifiedEmailPolicy// "", "require", "attest"AcceptableACR []stringAcceptableAMR []stringClaimsAreVerifiedByProvider []string
}
func (c*TrustContract) Enforce(ctx context.Context, claims*Claims) error {
ifc==nil { returnnil } // the whole guarantee of backward-compat// ... policy evaluation; herodot error with stable ID on reject ...
}
one guard, called once between claims parsing and newIdentityFromClaims. ~200 LOC total including validation + tests.
what's drafted
i've written a longer draft offline (not pushing yet): main doc (3,500 words) + Go reference impl (~195 LOC) + 30+ unit tests + a docker-compose attack-sim PoC (shows before/after) + threat model (STRIDE+LINDDUN) + compliance mapping to SOC 2 / NIST / FedRAMP controls + benchmark harness + observability spec (prom metrics + otel span) + default contracts bundle for 11 providers.
happy to open any subset as a formal RFC issue or PR series. before i do, three questions where community input would most change the shape:
scope. is this a Kratos concern or should it live in Hydra? i've assumed Kratos because the consumer-side guard is what's missing; happy to be told otherwise.
shape. declarative (what i've drafted) vs executable hook (Auth0 Actions / Clerk Rules shape). the two are complementary; declarative seemed cheaper to review and audit.
defaults. my draft ships ignore for every provider (zero behavior change). should Google/Apple ship require by default in a later minor, or should every tightening always be operator-opt-in?
not asking for
a decision today.
any change to current behavior for existing deployments (that's the point of the pointer being optional).
replacing jsonnet mappers (they run as before after the guard accepts).
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
Idea: opt-in
TrustContractfor OIDC provider configurationopening this as a Discussion (not a formal RFC issue) to float the shape and gather feedback before committing to a scope. the companion reliability fix for the error-flattening side of this is already in flight at #4626; this is the orthogonal "trust shape" piece.
one-line
add an optional
*TrustContractpointer onoidc.ProviderConfigthat lets operators declare their policy foremail_verified,acr,amrper provider. nil pointer = current behavior exactly. non-nil = declarative gate beforenewIdentityFromClaims.why
Kratos already defends against nOAuth-class account linking through three implicit layers (
OIDCUniqueIDcredential indexing,password.identifieruniqueness, duplicate-flow password gate). the defense works — but it is implicit, scattered acrossidentity/credentials_oidc.go:98, the default identity schema, andselfservice/strategy/oidc/strategy.go:702-731. operators who need to prove to auditors "we're not vulnerable to nOAuth" today walk reviewers through three code locations. a declarative per-provider contract would make that single-artifact.three in-tree providers also silently set
EmailVerified: trueon claims the provider does not actually verify (provider_facebook.go:120-122,provider_slack.go:87,provider_discord.go:98), and no consumer code reads the field — so the lie is harmless today but would be surprising to anyone who later tries to add a gate.#4581 showed the generic "any error becomes 401" pattern on the reliability side; the same code area has the orthogonal "any claim becomes trusted" pattern on the trust side. this discussion proposes the declarative fix for the second one.
sketch
one guard, called once between claims parsing and
newIdentityFromClaims. ~200 LOC total including validation + tests.what's drafted
i've written a longer draft offline (not pushing yet): main doc (3,500 words) + Go reference impl (~195 LOC) + 30+ unit tests + a docker-compose attack-sim PoC (shows before/after) + threat model (STRIDE+LINDDUN) + compliance mapping to SOC 2 / NIST / FedRAMP controls + benchmark harness + observability spec (prom metrics + otel span) + default contracts bundle for 11 providers.
happy to open any subset as a formal RFC issue or PR series. before i do, three questions where community input would most change the shape:
ignorefor every provider (zero behavior change). should Google/Apple shiprequireby default in a later minor, or should every tightening always be operator-opt-in?not asking for
author notes
feedback welcome. will not open a formal RFC issue until #4626 has landed and this thread has a direction.
All reactions