fix(dpop): handle 400 use_dpop_nonce on revoke path after cold restart (W-23501382) - #4145
Merged
Merged
Conversation
…t (W-23501382) After an app restart the in-memory DPoP-Nonce cache is empty. The first DPoP-decorated REST call (e.g. access-token revoke) sends a nonce-less proof; Salesforce's token endpoint replies with HTTP 400 use_dpop_nonce. The existing 401-refresh-replay path in SFRestAPI did not cover this case because shouldRetry only fires on 401/403. Fix: in SFRestAPI.enqueueRequest, detect a 400 response whose body contains "use_dpop_nonce", harvest the server-issued DPoP-Nonce from the response header into DPoPNonceCache, and re-enqueue the request once with the updated proof. A dpopNonceRetried flag on SFRestRequest prevents any subsequent challenge from looping. Enables test_givenDPoPUser_whenAppRestart_thenSessionAndKeypairSurvive in DPoPLoginTests (previously XCTSkip'd).
Clang Static Analysis Issues
Generated by 🚫 Danger |
|
||||||||||||||||
Codecov Report✅ All modified and coverable lines are covered by tests. ❌ Your patch check has failed because the patch coverage (40.00%) is below the target coverage (80.00%). You can increase the patch coverage or adjust the target coverage. Additional details and impacted files@@ Coverage Diff @@
## dev #4145 +/- ##
==========================================
- Coverage 71.61% 71.58% -0.03%
==========================================
Files 254 254
Lines 22837 22847 +10
==========================================
+ Hits 16355 16356 +1
- Misses 6482 6491 +9
🚀 New features to boost your workflow:
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
After a cold app restart the in-memory
DPoPNonceCacheis empty. The first DPoP-decorated REST call — most commonly the access-token revoke thatassertRevokeAndRefreshWorksissues — sends a nonce-less DPoP proof. Salesforce's authorization server replies with HTTP 400use_dpop_nonceand the desired nonce in theDPoP-Nonceresponse header.The existing retry path in
SFRestAPIonly handles 401 (expired credentials) and 403 Bad_OAuth_Token; it does not handle 400, so the revoke fails and the test throwsXCTAssertTrue failed - Failed to revoke access token.This was the root cause of the prematurely-closed W-23501382.
Fix
SFRestAPI.enqueueRequest— before the existingshouldRetryguard, detect a 400 response whose body containsuse_dpop_nonce, harvest the server-issued nonce from theDPoP-Nonceresponse header intoDPoPNonceCache, and re-enqueue the request once with the updated proof.SFRestRequest+Internal.h— addsdpopNonceRetriedBOOL flag so the retry fires at most once per request (prevents looping if the server keeps challenging).Per RFC 9449 §8 the server SHOULD include the desired nonce in the
DPoP-Nonceheader of the challenge response;harvestNonceFromResponse:requestURL:scope:is a no-op if the header is absent, so the retry falls through to the normal error path if the server doesn't cooperate.Test
DPoPLoginTests.test_givenDPoPUser_whenAppRestart_thenSessionAndKeypairSurvive— previouslyXCTSkip'd — is now enabled and passing:.thirdrestart())restartAndValidateUser— confirms EC keypair and session reloaded from Keychain/diskassertRevokeAndRefreshWorks— confirms revoke + refresh succeeds despite cold nonce cacheDPoPLoginTeststest_givenDPoPUser_whenAppRestart_thenSessionAndKeypairSurviveNotes