Multi-step sign-in
After a user has finished signup, they can proceed to sign in. Amplify Auth signin flows can be multi step processes. The required steps are determined by the configuration you provided when you ran amplify add auth
. Depending on the configuration, you may need to call various APIs to finish authenticating a user's signin attempt. To identify the next step in a signin flow, inspect the nextStep
parameter in the signin result.
When called successfully, the signin APIs will return an AuthSignInResult
. Inspect the nextStep
property in the result to see if additional signin steps are required.
1func signIn(username: String, password: String) async {2 do {3 let signInResult = try await Amplify.Auth.signIn(username: username, password: password)4 switch signInResult.nextStep {5 case .confirmSignInWithSMSMFACode(let deliveryDetails, let info):6 print("SMS code send to \(deliveryDetails.destination)")7 print("Additional info \(String(describing: info))")8
9 // Prompt the user to enter the SMSMFA code they received10 // Then invoke `confirmSignIn` api with the code11
12 case .confirmSignInWithTOTPCode:13 print("Received next step as confirm sign in with TOTP code")14
15 // Prompt the user to enter the TOTP code generated in their authenticator app16 // Then invoke `confirmSignIn` api with the code17 18 case .continueSignInWithTOTPSetup(let setUpDetails):19 print("Received next step as continue sign in by setting up TOTP")20 print("Shared secret that will be used to set up TOTP in the authenticator app \(setUpDetails.sharedSecret)")21 22 // Prompt the user to enter the TOTP code generated in their authenticator app23 // Then invoke `confirmSignIn` api with the code24
25 case .continueSignInWithMFASelection(let allowedMFATypes):26 print("Received next step as continue sign in by selecting MFA type")27 print("Allowed MFA types \(allowedMFATypes)")28 29 // Prompt the user to select the MFA type they want to use30 // Then invoke `confirmSignIn` api with the MFA type31 32 case .confirmSignInWithCustomChallenge(let info):33 print("Custom challenge, additional info \(String(describing: info))")34 35 // Prompt the user to enter custom challenge answer36 // Then invoke `confirmSignIn` api with the answer37 38 case .confirmSignInWithNewPassword(let info):39 print("New password additional info \(String(describing: info))")40 41 // Prompt the user to enter a new password42 // Then invoke `confirmSignIn` api with new password43 44 case .resetPassword(let info):45 print("Reset password additional info \(String(describing: info))")46 47 // User needs to reset their password.48 // Invoke `resetPassword` api to start the reset password49 // flow, and once reset password flow completes, invoke50 // `signIn` api to trigger signin flow again.51 52 case .confirmSignUp(let info):53 print("Confirm signup additional info \(String(describing: info))")54 55 // User was not confirmed during the signup process.56 // Invoke `confirmSignUp` api to confirm the user if57 // they have the confirmation code. If they do not have the58 // confirmation code, invoke `resendSignUpCode` to send the59 // code again.60 // After the user is confirmed, invoke the `signIn` api again.61 case .done:62 63 // Use has successfully signed in to the app64 print("Signin complete")65 }66 } catch let error as AuthError{67 print ("Sign in failed \(error)")68 } catch {69 print("Unexpected error: \(error)")70 }71}
The nextStep
property is of enum type AuthSignInStep
. Depending on its value, your code should take one of the following actions:
Confirm signin with SMS MFA
If the next step is confirmSignInWithSMSMFACode
, Amplify Auth has sent the user a random code over SMS, and is waiting to find out if the user successfully received it. To handle this step, your app's UI must prompt the user to enter the code. After the user enters the code, your implementation must pass the value to Amplify Auth confirmSignIn
API.
Note: the signin result also includes an AuthCodeDeliveryDetails
member. It includes additional information about the code delivery such as the partial phone number of the SMS recipient.
1func confirmSignIn(confirmationCodeFromUser: String) async {2 do {3 let signInResult = try await Amplify.Auth.confirmSignIn(challengeResponse: confirmationCodeFromUser)4 if signInResult.isSignedIn {5 print("Confirm sign in succeeded. The user is signed in.")6 } else {7 print("Confirm sign in succeeded.")8 print("Next step: \(signInResult.nextStep)")9 // Switch on the next step to take appropriate actions. 10 // If `signInResult.isSignedIn` is true, the next step 11 // is 'done', and the user is now signed in.12 }13 } catch let error as AuthError {14 print("Confirm sign in failed \(error)")15 } catch {16 print("Unexpected error: \(error)")17 }18}
1func confirmSignIn(confirmationCodeFromUser: String) -> AnyCancellable {2 Amplify.Publisher.create {3 try await Amplify.Auth.confirmSignIn(challengeResponse: confirmationCodeFromUser)4 }.sink {5 if case let .failure(authError) = $0 {6 print("Confirm sign in failed \(authError)")7 }8 }9 receiveValue: { signInResult in10 if signInResult.isSignedIn {11 print("Confirm sign in succeeded. The user is signed in.")12 } else {13 print("Confirm sign in succeeded.")14 print("Next step: \(signInResult.nextStep)")15 // Switch on the next step to take appropriate actions. 16 // If `signInResult.isSignedIn` is true, the next step 17 // is 'done', and the user is now signed in.18 }19 }20}
Confirm signin with TOTP MFA
If the next step is confirmSignInWithTOTPCode
, you should prompt the user to enter the TOTP code from their associated authenticator app during set up. The code is a six-digit number that changes every 30 seconds. The user must enter the code before the 30-second window expires.
After the user enters the code, your implementation must pass the value to Amplify Auth confirmSignIn
API.
1func confirmSignIn(totpCode: String) async {2 do {3 let signInResult = try await Amplify.Auth.confirmSignIn(challengeResponse: totpCode)4 if signInResult.isSignedIn {5 print("Confirm sign in succeeded. The user is signed in.")6 } else {7 print("Confirm sign in succeeded.")8 print("Next step: \(signInResult.nextStep)")9 // Switch on the next step to take appropriate actions. 10 // If `signInResult.isSignedIn` is true, the next step 11 // is 'done', and the user is now signed in.12 }13 } catch {14 print("Confirm sign in failed \(error)")15 }16}
1func confirmSignIn(totpCode: String) -> AnyCancellable {2 Amplify.Publisher.create {3 try await Amplify.Auth.confirmSignIn(challengeResponse: totpCode)4 }.sink {5 if case let .failure(authError) = $0 {6 print("Confirm sign in failed \(authError)")7 }8 }9 receiveValue: { signInResult in10 if signInResult.isSignedIn {11 print("Confirm sign in succeeded. The user is signed in.")12 } else {13 print("Confirm sign in succeeded.")14 print("Next step: \(signInResult.nextStep)")15 // Switch on the next step to take appropriate actions. 16 // If `signInResult.isSignedIn` is true, the next step 17 // is 'done', and the user is now signed in.18 }19 }20}
Continue signin with MFA Selection
If the next step is continueSignInWithMFASelection
, the user must select the MFA method to use. Amplify Auth currently only supports SMS and TOTP as MFA methods. After the user selects an MFA method, your implementation must pass the selected MFA method to Amplify Auth using confirmSignIn
API.
1func confirmSignInWithTOTPAsMFASelection() async {2 do {3 let signInResult = try await Amplify.Auth.confirmSignIn(4 challengeResponse: MFAType.totp.challengeResponse)5
6 if case .confirmSignInWithTOTPCode = signInResult.nextStep {7 print("Received next step as confirm sign in with TOTP")8 }9
10 } catch {11 print("Confirm sign in failed \(error)")12 }13}
1func confirmSignInWithTOTPAsMFASelection() -> AnyCancellable {2 Amplify.Publisher.create {3 try await Amplify.Auth.confirmSignIn(4 challengeResponse: MFAType.totp.challengeResponse)5 }.sink {6 if case let .failure(authError) = $0 {7 print("Confirm sign in failed \(authError)")8 }9 }10 receiveValue: { signInResult in11 if case .confirmSignInWithTOTPCode = signInResult.nextStep {12 print("Received next step as confirm sign in with TOTP")13 }14 }15}
Continue signin with TOTP Setup
If the next step is continueSignInWithTOTPSetup
, then the user must provide a TOTP code to complete the sign in process. The step returns an associated value of type TOTPSetupDetails
which would be used for generating TOTP. TOTPSetupDetails
provides a helper method called getSetupURI
that can be used to generate a URI, which can be used by native password managers for TOTP association. For example. if the URI is used on Apple platforms, it will trigger the platform's native password manager to associate TOTP with the account. For more advanced use cases, TOTPSetupDetails
also contains the sharedSecret
that will be used to either generate a QR code or can be manually entered into an authenticator app.
Once the authenticator app is set up, the user can generate a TOTP code and provide it to the library to complete the sign in process.
1// Confirm sign in with TOTP setup2case .continueSignInWithTOTPSetup(let setUpDetails):3 4 /// appName parameter will help distinguish the account in the Authenticator app5 let setupURI = try setUpDetails.getSetupURI(appName: "<Your_App_Name>>") 6 7 print("TOTP Setup URI: \(setupURI)")
1func confirmSignInWithTOTPSetup(totpCodeFromAuthenticatorApp: String) async {2 do {3 let signInResult = try await Amplify.Auth.confirmSignIn(4 challengeResponse: totpCodeFromAuthenticatorApp)5
6 if signInResult.isSignedIn {7 print("Confirm sign in succeeded. The user is signed in.")8 } else {9 print("Confirm sign in succeeded.")10 print("Next step: \(signInResult.nextStep)")11 // Switch on the next step to take appropriate actions. 12 // If `signInResult.isSignedIn` is true, the next step 13 // is 'done', and the user is now signed in.14 }15 } catch {16 print("Confirm sign in failed \(error)")17 }18}
1func confirmSignInWithTOTPSetup(totpCodeFromAuthenticatorApp: String) -> AnyCancellable {2 Amplify.Publisher.create {3 try await Amplify.Auth.confirmSignIn(4 challengeResponse: totpCodeFromAuthenticatorApp)5 }.sink {6 if case let .failure(authError) = $0 {7 print("Confirm sign in failed \(authError)")8 }9 }10 receiveValue: { signInResult in11 if signInResult.isSignedIn {12 print("Confirm sign in succeeded. The user is signed in.")13 } else {14 print("Confirm sign in succeeded.")15 print("Next step: \(signInResult.nextStep)")16 // Switch on the next step to take appropriate actions. 17 // If `signInResult.isSignedIn` is true, the next step 18 // is 'done', and the user is now signed in.19 }20 }21}
Confirm signin with custom challenge
If the next step is confirmSignInWithCustomChallenge
, Amplify Auth is awaiting completion of a custom authentication challenge. The challenge is based on the Lambda trigger you setup when you configured a custom sign in flow. To complete this step, you should prompt the user for the custom challenge answer, and pass the answer to the confirmSignIn
API.
1func confirmSignIn(challengeAnswerFromUser: String) async {2 do {3 let signInResult = try await Amplify.Auth.confirmSignIn(challengeResponse: challengeAnswerFromUser)4 if signInResult.isSignedIn {5 print("Confirm sign in succeeded. The user is signed in.")6 } else {7 print("Confirm sign in succeeded.")8 print("Next step: \(signInResult.nextStep)")9 // Switch on the next step to take appropriate actions.10 // If `signInResult.isSignedIn` is true, the next step11 // is 'done', and the user is now signed in.12 }13 } catch let error as AuthError {14 print("Confirm sign in failed \(error)")15 } catch {16 print("Unexpected error: \(error)")17 }18}
1func confirmSignIn(challengeAnswerFromUser: String) -> AnyCancellable {2 Amplify.Publisher.create {3 try await Amplify.Auth.confirmSignIn(challengeResponse: challengeAnswerFromUser)4 }.sink {5 if case let .failure(authError) = $0 {6 print("Confirm sign in failed \(authError)")7 }8 }9 receiveValue: { signInResult in10 if signInResult.isSignedIn {11 print("Confirm sign in succeeded. The user is signed in.")12 } else {13 print("Confirm sign in succeeded.")14 print("Next step: \(signInResult.nextStep)")15 // Switch on the next step to take appropriate actions.16 // If `signInResult.isSignedIn` is true, the next step17 // is 'done', and the user is now signed in.18 }19 }20}
Confirm signin with new password
If the next step is confirmSignInWithNewPassword
, Amplify Auth requires a new password for the user before they can proceed. Prompt the user for a new password and pass it to the confirmSignIn
API.
1func confirmSignIn(newPasswordFromUser: String) async {2 do {3 let signInResult = try await Amplify.Auth.confirmSignIn(challengeResponse: newPasswordFromUser)4 if signInResult.isSignedIn {5 print("Confirm sign in succeeded. The user is signed in.")6 } else {7 print("Confirm sign in succeeded.")8 print("Next step: \(signInResult.nextStep)")9 // Switch on the next step to take appropriate actions. 10 // If `signInResult.isSignedIn` is true, the next step 11 // is 'done', and the user is now signed in.12 }13 } catch let error as AuthError {14 print("Confirm sign in failed \(error)")15 } catch {16 print("Unexpected error: \(error)")17 }18}
1func confirmSignIn(newPasswordFromUser: String) -> AnyCancellable {2 Amplify.Publisher.create {3 try await Amplify.Auth.confirmSignIn(challengeResponse: newPasswordFromUser)4 }.sink {5 if case let .failure(authError) = $0 {6 print("Confirm sign in failed \(authError)")7 }8 }9 receiveValue: { signInResult in10 if signInResult.isSignedIn {11 print("Confirm sign in succeeded. The user is signed in.")12 } else {13 print("Confirm sign in succeeded.")14 print("Next step: \(signInResult.nextStep)")15 // Switch on the next step to take appropriate actions. 16 // If `signInResult.isSignedIn` is true, the next step 17 // is 'done', and the user is now signed in.18 }19 }20}
Reset password
If you receive resetPassword
, authentication flow could not proceed without resetting the password. The next step is to invoke resetPassword
api and follow the reset password flow.
1func resetPassword(username: String) async {2 do {3 let resetPasswordResult = try await Amplify.Auth.resetPassword(for: username)4 print("Reset password succeeded.")5 print("Next step: \(resetPasswordResult.nextStep)")6 } catch let error as AuthError {7 print("Reset password failed \(error)")8 } catch {9 print("Unexpected error: \(error)")10 }11}
1func resetPassword(username: String) -> AnyCancellable {2 Amplify.Publisher.create {3 try await Amplify.Auth.resetPassword(for: username)4 }.sink {5 if case let .failure(authError) = $0 {6 print("Reset password failed \(authError)")7 }8 }9 receiveValue: { resetPasswordResult in10 print("Reset password succeeded.")11 print("Next step: \(resetPasswordResult.nextStep)")12 }13}
Confirm Signup
If you receive confirmSignUp
as a next step, sign up could not proceed without confirming user information such as email or phone number. The next step is to invoke the confirmSignUp
API and follow the confirm signup flow.
1func confirmSignUp(for username: String, with confirmationCode: String) async {2 do {3 let confirmSignUpResult = try await Amplify.Auth.confirmSignUp(4 for: username,5 confirmationCode: confirmationCode6 )7 print("Confirm sign up result completed: \(confirmSignUpResult.isSignUpComplete)")8 } catch let error as AuthError {9 print("An error occurred while confirming sign up \(error)")10 } catch {11 print("Unexpected error: \(error)")12 }13}
1func confirmSignUp(for username: String, with confirmationCode: String) -> AnyCancellable {2 Amplify.Publisher.create {3 try await Amplify.Auth.confirmSignUp(for: username, confirmationCode: confirmationCode)4 }.sink {5 if case let .failure(authError) = $0 {6 print("An error occurred while confirming sign up \(authError)")7 }8 }9 receiveValue: { _ in10 print("Confirm signUp succeeded")11 }12}
Done
Signin flow is complete when you get done
. This means the user is successfully authenticated. As a convenience, the SignInResult also provides the isSignedIn
property, which will be true if the next step is done
.