@@ -38,6 +38,11 @@ public class SignInSelectAuthFactorState: AuthenticatorBaseState {
3838 ///
3939 /// Automatically sets the Authenticator's next step accordingly, as well as the
4040 /// ``AuthenticatorBaseState/isBusy`` and ``AuthenticatorBaseState/message`` properties.
41+ ///
42+ /// If the user has already selected an auth factor previously (tracked via `credentials.selectedAuthFactor`),
43+ /// this method will restart the sign-in flow with the new factor as the preferred first factor.
44+ /// This is necessary because Cognito doesn't allow changing the auth factor selection once made.
45+ ///
4146 /// - Throws: An `Amplify.AuthenticationError` if the operation fails
4247 public func selectAuthFactor( ) async throws {
4348 guard let factor = selectedAuthFactor else {
@@ -51,50 +56,28 @@ public class SignInSelectAuthFactorState: AuthenticatorBaseState {
5156 do {
5257 log. verbose ( " Selecting auth factor: \( factor) " )
5358
59+ // Check if user has already selected an auth factor previously
60+ // If so, we need to restart the sign-in flow instead of calling confirmSignIn
61+ let flowRestartRequired = credentials. selectedAuthFactor != nil
62+
63+ // Update password in credentials if password factor is selected
64+ if factor. isPassword {
65+ credentials. password = password
66+ }
67+
68+ // Track the selected auth factor
69+ credentials. selectedAuthFactor = factor
70+
5471 let result : AuthSignInResult
5572
56- switch factor {
57- case . password:
58- // Password requires 2-step flow, use dedicated method
59- // Step 1: Select password factor → confirmSignIn("PASSWORD") → .confirmSignInWithPassword
60- // Step 2: Send password → confirmSignIn("Pass@123") → .done
61- result = try await signInWithPassword ( )
62-
63- case . emailOtp, . smsOtp:
64- // Select the auth factor and move to appropriate next step
65- // Use the AuthFactor extension to get the challenge response
66- let challengeResponse = factor. toAuthFactorType ( ) . challengeResponse
67-
68- result = try await authenticationService. confirmSignIn (
69- challengeResponse: challengeResponse,
70- options: nil
71- )
72-
73- case . webAuthn:
74- // WebAuthn sign-in - Amplify handles the native UI
75- #if os(iOS) || os(macOS) || os(visionOS)
76- guard #available( iOS 17 . 4 , macOS 13 . 5 , visionOS 1 . 0 , * ) else {
77- setBusy ( false )
78- log. error ( " WebAuthn requires iOS 17.4+, macOS 13.5+, or visionOS 1.0+ " )
79- setMessage ( . error( message: " Passkey is not available " ) )
80- return
81- }
82-
83- log. verbose ( " Initiating WebAuthn sign-in " )
84-
85- // Select WebAuthn as the auth factor
86- let challengeResponse = factor. toAuthFactorType ( ) . challengeResponse
87-
88- result = try await authenticationService. confirmSignIn (
89- challengeResponse: challengeResponse,
90- options: nil
91- )
92- #else
93- setBusy ( false )
94- log. error ( " WebAuthn is not available on this platform " )
95- setMessage ( . error( message: " Passkey is not available " ) )
96- return
97- #endif
73+ if flowRestartRequired {
74+ // User has already selected an auth factor before
75+ // Restart sign-in flow with the new factor as preferred
76+ log. verbose ( " Restarting sign-in flow with preferred factor: \( factor) " )
77+ result = try await restartSignInWithPreferredFactor ( factor)
78+ } else {
79+ // First-time selection - use confirmSignIn as normal
80+ result = try await confirmSignInWithFactor ( factor)
9881 }
9982
10083 let nextStep = try await nextStep ( for: result)
@@ -108,6 +91,69 @@ public class SignInSelectAuthFactorState: AuthenticatorBaseState {
10891 }
10992 }
11093
94+ /// Confirms sign-in with the selected auth factor (first-time selection)
95+ /// - Parameter factor: The auth factor to use
96+ /// - Returns: The `AuthSignInResult` from the confirmation
97+ private func confirmSignInWithFactor( _ factor: AuthFactor ) async throws -> AuthSignInResult {
98+ switch factor {
99+ case . password:
100+ // Password requires 2-step flow, use dedicated method
101+ // Step 1: Select password factor → confirmSignIn("PASSWORD") → .confirmSignInWithPassword
102+ // Step 2: Send password → confirmSignIn("Pass@123") → .done
103+ return try await signInWithPassword ( )
104+
105+ case . emailOtp, . smsOtp:
106+ // Select the auth factor and move to appropriate next step
107+ // Use the AuthFactor extension to get the challenge response
108+ let challengeResponse = factor. toAuthFactorType ( ) . challengeResponse
109+
110+ return try await authenticationService. confirmSignIn (
111+ challengeResponse: challengeResponse,
112+ options: nil
113+ )
114+
115+ case . webAuthn:
116+ // WebAuthn sign-in - Amplify handles the native UI
117+ #if os(iOS) || os(macOS) || os(visionOS)
118+ guard #available( iOS 17 . 4 , macOS 13 . 5 , visionOS 1 . 0 , * ) else {
119+ log. error ( " WebAuthn requires iOS 17.4+, macOS 13.5+, or visionOS 1.0+ " )
120+ throw AuthError . unknown ( " Passkey is not available " , nil )
121+ }
122+
123+ log. verbose ( " Initiating WebAuthn sign-in " )
124+
125+ // Select WebAuthn as the auth factor
126+ let challengeResponse = factor. toAuthFactorType ( ) . challengeResponse
127+
128+ return try await authenticationService. confirmSignIn (
129+ challengeResponse: challengeResponse,
130+ options: nil
131+ )
132+ #else
133+ log. error ( " WebAuthn is not available on this platform " )
134+ throw AuthError . unknown ( " Passkey is not available " , nil )
135+ #endif
136+ }
137+ }
138+
139+ /// Restarts the sign-in flow with the specified factor as the preferred first factor.
140+ /// This is used when the user changes their auth factor selection after already selecting one.
141+ /// - Parameter factor: The auth factor to use as the preferred first factor
142+ /// - Returns: The `AuthSignInResult` from the sign-in attempt
143+ private func restartSignInWithPreferredFactor( _ factor: AuthFactor ) async throws -> AuthSignInResult {
144+ let options = AuthSignInRequest . Options (
145+ pluginOptions: AWSAuthSignInOptions (
146+ authFlowType: . userAuth( preferredFirstFactor: factor. toAuthFactorType ( ) )
147+ )
148+ )
149+
150+ return try await authenticationService. signIn (
151+ username: credentials. username,
152+ password: factor. isPassword ? credentials. password : nil ,
153+ options: options
154+ )
155+ }
156+
111157 /// Signs in with password using the multi-step flow
112158 ///
113159 /// Password flow:
0 commit comments