feat(dpop): send dpop_jkt in /authorize for my-domain servers (W-23406836)#2965
Conversation
…6836) When useDPoP=true and the login server is a custom/my-domain server, generate credentialsIdentifier eagerly in generateAuthorizationUrl(), load the EC key pair, compute the RFC 7638 JWK thumbprint, and add dpop_jkt=<thumbprint> to the authorization URL parameters. The identifier is stored as pendingCredentialsIdentifier so doCodeExchange() reuses it, preserving the key pair binding from authorize -> token exchange. Pool servers (login.salesforce.com, test.salesforce.com, welcome.salesforce.com) are excluded — they don't support DPoP code binding, matching the same guard already used in fetchAuthenticationConfiguration() for browser login and app attestation.
Generated by 🚫 Danger |
… stubs and server URLs (W-23406836) - Rename helper to addDpopJktIfNeeded for clarity - Fix DPoP unit tests: use my-domain URL (myorg.my.salesforce.com) instead of test.salesforce.com so dpop_jkt is not excluded by the pool-server guard - Add missing useDPoP stub to two strict (relaxed=false) mock tests
sfdctaka
left a comment
There was a problem hiding this comment.
Non-blocking observations on four edge cases in addDpopJktIfNeeded and doCodeExchange. All happy-path flows look correct; these only bite under specific re-entry / retry sequences. Flagging for awareness, not requesting changes.
…erver re-entry (W-23406836) - On early return (pool server or DPoP disabled), remove any previous dpop_jkt from params, delete the orphaned AndroidKeyStore entry, and clear pendingCredentialsIdentifier so the two stay in sync - On my-domain happy path, delete the previous orphaned key before generating a new one so repeated server-picker navigation doesn't accumulate unbounded keystore entries
| val isMyDomainServer = server != PRODUCTION_LOGIN_URL | ||
| && server != SANDBOX_LOGIN_URL | ||
| && server != WELCOME_LOGIN_URL |
There was a problem hiding this comment.
NIT: I think we do this check in a couple places. Could be worth creating a static utility somewhere (if we don't already have one?).
Consolidates the repeated three-way pool-server check (PRODUCTION / SANDBOX / WELCOME) into a single static method on LoginServerManager, and updates all three call sites: - LoginViewModel.addDpopJktIfNeeded - SalesforceSDKManager.fetchAndUpdateAuthConfigIfNeeded - SalesforceDroidGapActivity.fetchAuthConfig
Generated by 🚫 Danger |
…W-23406836) Decouples the login surface selection (Custom Tab vs in-app WebView) from useWebServerFlow. Moves the new parameter after knownUserConfig so existing positional call sites are unaffected.
|
@brandonpage what do you think of 7d3b035 ? (It probably could have gone to a separate PR though). |
Seems fine. I didn't want to over invest in opt-out WebView scenarios, but there is no harm in it. |
… AuthFlowTester (W-23406836) - AndroidManifest: add missing intent filters for ecajwtrtr, ecaopaquertr, ecajwtdpop, ecajwtdpoprtr - AuthFlowTest: forward forceAdvancedAuthentication as expectAdvancedAuth in loginAndValidate/validateUser - AuthFlowTest: add expectAdvancedAuth/isDpop/isMultiUser params to assertRevokeAndRefreshWorks, migrateAndValidate, restartAndValidateUser, switchToUserAndValidateUser - AuthFlowTest: fix adminLoginAndValidate to forward useDPoP to validateUser - AuthFlowTest: remove stale nonce-change assertion (server issues nonces with 24h TTL, not per-refresh) - DPoPLoginTests: pass isDpop/isMultiUser at all call sites; ignore RTR+hybrid test (W-22512846); use useHybridAuthToken=false for DPoP→DPoPRtr migration
Summary
Core feature: `dpop_jkt` in `/authorize` (W-23406836)
Without this fix the server rejects with:
```
error=invalid_request&error_description=missing required dpop_jkt for code binding
```
Supporting refactors
AuthFlowTester improvements
Test plan