Skip to content

Fix 2FA authentication regression causing 403 on download (#480)#481

Merged
MattKiazyk merged 1 commit into
XcodesOrg:mainfrom
kinoroy:fix-2fa-authentication-regression
Jun 17, 2026
Merged

Fix 2FA authentication regression causing 403 on download (#480)#481
MattKiazyk merged 1 commit into
XcodesOrg:mainfrom
kinoroy:fix-2fa-authentication-regression

Conversation

@kinoroy

@kinoroy kinoroy commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes #480 — accounts with two-factor authentication get a 403: Unauthorized on download because the CLI never completes the 2FA flow (no verification-code prompt appears).

Root cause

In 1.6.x, Client.srpLogin drove the entire interactive 2FA flow internally — on Apple's 409 response it prompted for the verification code, submitted it, trusted the session, and only returned once fully authenticated.

In 2.x, login moved to the external XcodesLoginKit package and was redesigned as a state machine. srpLogin now returns an AuthenticationState (e.g. .waitingForSecondFactor) instead of completing the flow — the caller is expected to drive the second factor (the SwiftUI app does this). But the CLI's login closures in Environment.swift discarded the returned state:

_ = try await loginClient.srpLogin(accountName: accountName, password: password)

So for any 2FA account: srpLogin returned .waitingForSecondFactor without throwing → AppleSessionService.loginIfNeeded treated it as success → the cookie jar held only the pre-2FA session → the download hit Apple's 403 Unauthorized. The missing 2FA prompt was the visible symptom; the 403 was the downstream consequence. Non-2FA accounts were unaffected, which matches the report that this is a regression specific to 2FA accounts.

The fix

  • TwoFactorAuthentication.swift (new): Ports 1.6.x's interactive completion onto the new state-machine API. completeIfNeeded(_:dependencies:) inspects the returned state and handles every path 1.6.x supported:

    • Trusted-device code (with "sms" fallback to phone-number selection)
    • SMS auto-sent to a single trusted number
    • SMS phone-number selection (lists numbers, requests the code, retries on invalid index)
    • Security key and federated / not-developer states surface clear errors (1.6.x never supported hardware keys either).

    Modeled with injected closures (Dependencies) rather than the concrete Client, matching the repo's existing dependency-injection style and keeping it unit-testable.

  • Environment.swift: Both login closures now capture the returned state and call TwoFactorAuthentication.completeIfNeeded(...).

  • TwoFactorAuthenticationTests.swift (new): 8 tests covering each branch.

Testing

  • swift test87 passed, 0 failures (was 79; +8 new).
  • Manually verified xcodes install end-to-end against a real Apple ID with SMS 2FA and against one with trusted-device 2FA — both now prompt for the code and authenticate successfully.

In 1.6.x, Client.srpLogin drove the entire interactive two-factor flow
internally and only returned once the session was fully authenticated.
In 2.x, login moved to XcodesLoginKit and srpLogin now returns an
AuthenticationState instead of completing the flow, expecting the caller
to handle the second factor. The CLI's login closures discarded that
state, so 2FA accounts were left with an unauthenticated (pre-2FA)
cookie jar: no verification-code prompt appeared and downloads failed
with 403 Unauthorized.

Restore the interactive completion for the paths 1.6.x supported:
trusted-device codes, SMS auto-sent, and SMS phone-number selection.
Security-key and federated/not-developer states surface clear errors.

The flow is modeled with injected closures (Dependencies) rather than a
concrete Client to match the existing dependency-injection style and to
keep it unit-testable. Adds coverage for each branch.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@kinoroy kinoroy force-pushed the fix-2fa-authentication-regression branch from bdaec97 to 3c5d5ed Compare June 16, 2026 19:58
@MattKiazyk

Copy link
Copy Markdown
Contributor

Thanks @kinoroy - I'll try to get this merged and built soon!

@MattKiazyk MattKiazyk merged commit 1b23a7e into XcodesOrg:main Jun 17, 2026
1 check passed
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.

403: Unauthorized received on accounts with two-factor enabled.

2 participants