From 9111400249ae4886a09c658a32717b65229d6474 Mon Sep 17 00:00:00 2001 From: Matt Creaser Date: Wed, 11 Jun 2025 11:35:33 -0300 Subject: [PATCH 1/7] Add swift content for WebAuthn Passkeys --- .../connect-your-frontend/sign-in/index.mdx | 64 ++++++++++++++++++- 1 file changed, 63 insertions(+), 1 deletion(-) diff --git a/src/pages/[platform]/build-a-backend/auth/connect-your-frontend/sign-in/index.mdx b/src/pages/[platform]/build-a-backend/auth/connect-your-frontend/sign-in/index.mdx index d8efe532254..5a0b986f389 100644 --- a/src/pages/[platform]/build-a-backend/auth/connect-your-frontend/sign-in/index.mdx +++ b/src/pages/[platform]/build-a-backend/auth/connect-your-frontend/sign-in/index.mdx @@ -1697,7 +1697,69 @@ Using WebAuthn sign in may result in a number of possible exception types. -{/* */} + + + +```swift +// sign in with `webAuthn` as preferred factor +func signIn(username: String) async { + do { + let authFactorType : AuthFactorType + if #available(iOS 17.4, *) { + authFactorType = .webAuthn + } else { + // Fallback on earlier versions + authFactorType = .passwordSRP + } + + let signInResult = try await Amplify.Auth.signIn( + username: username, + options: .init(pluginOptions: AWSAuthSignInOptions( + authFlowType: .userAuth( + preferredFirstFactor: authFactorType)))) + print("Sign in succeeded. Next step: \(signInResult.nextStep)") + } catch let error as AuthError { + print("Sign in failed \(error)") + } catch { + print("Unexpected error: \(error)") + } +} + +``` + + + + +```swift +// sign in with `webAuthn` as preferred factor +func signIn(username: String) async { + Amplify.Publisher.create { + let authFactorType : AuthFactorType + if #available(iOS 17.4, *) { + authFactorType = .webAuthn + } else { + // Fallback on earlier versions + authFactorType = .passwordSRP + } + + try await Amplify.Auth.signIn( + username: username, + options: .init(pluginOptions: AWSAuthSignInOptions( + authFlowType: .userAuth( + preferredFirstFactor: authFactorType)))) + }.sink { + if case let .failure(authError) = $0 { + print("Sign in failed \(authError)") + } + } + receiveValue: { signInResult in + print("Sign in succeeded. Next step: \(signInResult.nextStep)") + } +} +``` + + + From ea8eecb6acc8e8edeaf3dcb6b0e0bb30e34942a2 Mon Sep 17 00:00:00 2001 From: Matt Creaser Date: Wed, 11 Jun 2025 11:35:52 -0300 Subject: [PATCH 2/7] Add managing credentials blurb --- .../build-a-backend/auth/concepts/passwordless/index.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/[platform]/build-a-backend/auth/concepts/passwordless/index.mdx b/src/pages/[platform]/build-a-backend/auth/concepts/passwordless/index.mdx index 6fc490e7ca0..1c92cf823ab 100644 --- a/src/pages/[platform]/build-a-backend/auth/concepts/passwordless/index.mdx +++ b/src/pages/[platform]/build-a-backend/auth/concepts/passwordless/index.mdx @@ -144,6 +144,6 @@ WebAuthn uses biometrics or security keys for authentication, leveraging device- ### Managing credentials -{/* quick blurb then segue over to "manage WebAuthn credentials" page */} +Passwordless authentication with WebAuthn requires associating one or more credentials with the user's Amazon Cognito account. Amplify provides APIs that integrate with each platform's authenticator mechanism to easily create, view, and delete these credential assocations. [Learn more about managing WebAuthn credentials](/[platform]/build-a-backend/auth/manage-users/manage-webauthn-credentials). From 3cfac051870009d3c46aca103b3023585a41b8b1 Mon Sep 17 00:00:00 2001 From: Matt Creaser Date: Wed, 11 Jun 2025 11:36:12 -0300 Subject: [PATCH 3/7] Add more details about setting up passkeys for Android --- .../auth/concepts/passwordless/index.mdx | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/pages/[platform]/build-a-backend/auth/concepts/passwordless/index.mdx b/src/pages/[platform]/build-a-backend/auth/concepts/passwordless/index.mdx index 1c92cf823ab..0e06047b5a6 100644 --- a/src/pages/[platform]/build-a-backend/auth/concepts/passwordless/index.mdx +++ b/src/pages/[platform]/build-a-backend/auth/concepts/passwordless/index.mdx @@ -42,7 +42,7 @@ Passwordless authentication removes the security risks and user friction associa -Learn how to implement passwordless sign-in flows by [overriding the Cognito UserPool to enable the sign-in methods below](/[platform]/build-a-backend/auth/modify-resources-with-cdk/#override-cognito-userpool-to-enable-passwordless-sign-in-methods). +Learn how to enable passwordless sign-in flows by [overriding the Cognito UserPool to enable the sign-in methods below](/[platform]/build-a-backend/auth/modify-resources-with-cdk/#override-cognito-userpool-to-enable-passwordless-sign-in-methods). {/* need a section about what a "preferred" factor is */} @@ -131,7 +131,17 @@ WebAuthn uses biometrics or security keys for authentication, leveraging device- -{/* */} +You can read more about how passkeys work in the [Android developer docs](https://developer.android.com/design/ui/mobile/guides/patterns/passkeys). + + +Registering a passkey is supported on Android 9 (API level 28) and above. + + +Using passkeys with Amplify Android requires following these steps: + +1. Deploy a Digital Asset Links file to your website granting the `get_login_creds` permission to your application. See the [Credential Manager documentation](https://developer.android.com/identity/sign-in/credential-manager#add-support-dal) for more details about this file. +2. [Configure your Amazon Cognito user pool](/[platform]/build-a-backend/auth/modify-resources-with-cdk/#override-cognito-userpool-to-enable-passwordless-sign-in-methods) with `WEB_AUTHN` as an allowed first factor, and specify your website domain as the `WebAuthnRelyingPartyID`. +3. Use the Amplify Android APIs to first [register a passkey](/[platform]/build-a-backend/auth/manage-users/manage-webauthn-credentials/#associate-webauthn-credentials) and then to [sign in with WebAuthn](/[platform]/build-a-backend/auth/connect-your-frontend/sign-in/#webauthn-passkeys). From 37673d00eb2949eef68f62a7c83d4789af695d8c Mon Sep 17 00:00:00 2001 From: Matt Creaser Date: Wed, 11 Jun 2025 12:54:14 -0300 Subject: [PATCH 4/7] Improve passwordless docs for Android --- .../auth/concepts/passwordless/index.mdx | 6 +- .../connect-your-frontend/sign-in/index.mdx | 574 +++++++++++------- .../manage-webauthn-credentials/index.mdx | 22 +- .../auth/modify-resources-with-cdk/index.mdx | 7 +- 4 files changed, 377 insertions(+), 232 deletions(-) diff --git a/src/pages/[platform]/build-a-backend/auth/concepts/passwordless/index.mdx b/src/pages/[platform]/build-a-backend/auth/concepts/passwordless/index.mdx index 0e06047b5a6..f256364986e 100644 --- a/src/pages/[platform]/build-a-backend/auth/concepts/passwordless/index.mdx +++ b/src/pages/[platform]/build-a-backend/auth/concepts/passwordless/index.mdx @@ -137,11 +137,11 @@ You can read more about how passkeys work in the [Android developer docs](https: Registering a passkey is supported on Android 9 (API level 28) and above. -Using passkeys with Amplify Android requires following these steps: +Using passkeys with Amplify requires following these steps: 1. Deploy a Digital Asset Links file to your website granting the `get_login_creds` permission to your application. See the [Credential Manager documentation](https://developer.android.com/identity/sign-in/credential-manager#add-support-dal) for more details about this file. -2. [Configure your Amazon Cognito user pool](/[platform]/build-a-backend/auth/modify-resources-with-cdk/#override-cognito-userpool-to-enable-passwordless-sign-in-methods) with `WEB_AUTHN` as an allowed first factor, and specify your website domain as the `WebAuthnRelyingPartyID`. -3. Use the Amplify Android APIs to first [register a passkey](/[platform]/build-a-backend/auth/manage-users/manage-webauthn-credentials/#associate-webauthn-credentials) and then to [sign in with WebAuthn](/[platform]/build-a-backend/auth/connect-your-frontend/sign-in/#webauthn-passkeys). +1. [Configure your Amazon Cognito user pool](/[platform]/build-a-backend/auth/modify-resources-with-cdk/#override-cognito-userpool-to-enable-passwordless-sign-in-methods) with `WEB_AUTHN` as an allowed first factor, and specify your website domain as the `WebAuthnRelyingPartyID`. +1. Use the Amplify Android APIs to first [register a passkey](/[platform]/build-a-backend/auth/manage-users/manage-webauthn-credentials/#associate-webauthn-credentials) and then to [sign in with WebAuthn](/[platform]/build-a-backend/auth/connect-your-frontend/sign-in/#webauthn-passkeys). diff --git a/src/pages/[platform]/build-a-backend/auth/connect-your-frontend/sign-in/index.mdx b/src/pages/[platform]/build-a-backend/auth/connect-your-frontend/sign-in/index.mdx index 5a0b986f389..25580fd5653 100644 --- a/src/pages/[platform]/build-a-backend/auth/connect-your-frontend/sign-in/index.mdx +++ b/src/pages/[platform]/build-a-backend/auth/connect-your-frontend/sign-in/index.mdx @@ -1140,7 +1140,7 @@ func socialSignInWithWebUI() -> AnyCancellable { ## Sign in with passwordless methods -Your application's users can also sign in using passwordless methods. To learn more, visit the [concepts page for passwordless](/[platform]/build-a-backend/auth/concepts/passwordless/). +Your application's users can also sign in using passwordless methods. To learn more, including how to setup the various passwordless authentication flows, visit the [concepts page for passwordless](/[platform]/build-a-backend/auth/concepts/passwordless/). ### SMS OTP @@ -1175,17 +1175,24 @@ if (signInNextStep.signInStep === 'CONFIRM_SIGN_IN_WITH_SMS_CODE') { -To request an OTP code via SMS for authentication, you pass the `challengeResponse` for `AuthFactorType.SMS_OTP` to the `confirmSignIn` API. +Pass `SMS_OTP` as the `preferredFirstFactor` when calling the `signIn` API in order to initiate a passwordless authentication flow with SMS OTP. -Amplify will respond appropriately to Cognito and return the challenge as the sign in next step: `CONFIRM_SIGN_IN_WITH_OTP_CODE`. You will call `confirmSignIn` again, this time with the OTP that your user provides. ```java -// First confirm the challenge type -Amplify.Auth.confirmSignIn( - AuthFactorType.SMS_OTP.getChallengeResponse(), +// Use options to specify the preferred first factor +AWSCognitoAuthSignInOptions options = AWSCognitoAuthSignInOptions.builder() + .authFlowType(AuthFlowType.USER_AUTH) + .preferredFirstFactor(AuthFactorType.SMS_OTP) // Sign in using SMS OTP + .build(); + +// Sign in the user +Amplify.Auth.signIn( + username, + null, // no password + options, result -> { if (result.getNextStep().getSignInStep() == AuthSignInStep.CONFIRM_SIGN_IN_WITH_OTP) { // Show UI to collect OTP @@ -1208,28 +1215,30 @@ Amplify.Auth.confirmSignIn( ```kotlin -// First confirm the challenge type -Amplify.Auth.confirmSignIn( - AuthFactorType.SMS_OTP.challengeResponse, - { result -> +// Use options to specify the preferred first factor +val options = AWSCognitoAuthSignInOptions.builder() + .authFlowType(AuthFlowType.USER_AUTH) + .preferredFirstFactor(AuthFactorType.SMS_OTP) // Sign in using SMS OTP + .build() + +// Sign in the user +Amplify.Auth.signIn( + username, + null, // no password + options, + { result: AuthSignInResult -> if (result.nextStep.signInStep == AuthSignInStep.CONFIRM_SIGN_IN_WITH_OTP) { // Show UI to collect OTP } }, - { error -> - Log.e("AuthQuickstart", "Failed to sign in", error) - } + { error: AuthException -> Log.e("AuthQuickstart", error.toString()) } ) // Then pass that OTP into the confirmSignIn API Amplify.Auth.confirmSignIn( "123456", - { result -> - // result.nextStep.signInStep should be "DONE" now - }, - { error -> - Log.e("AuthQuickstart", "Failed to sign in", error) - } + { result: AuthSignInResult? -> }, + { error: AuthException -> Log.e("AuthQuickstart", error.toString()) } ) ``` @@ -1237,32 +1246,52 @@ Amplify.Auth.confirmSignIn( ```kotlin -// First confirm the challenge type -var result = Amplify.Auth.confirmSignIn(AuthFactorType.SMS_OTP.challengeResponse) +// Use options to specify the preferred first factor +val options = AWSCognitoAuthSignInOptions.builder() + .authFlowType(AuthFlowType.USER_AUTH) + .preferredFirstFactor(AuthFactorType.SMS_OTP) // Sign in using SMS OTP + .build() + +// Sign in the user +val result = Amplify.Auth.signIn( + username = username, + password = null, + options = options +) if (result.nextStep.signInStep == AuthSignInStep.CONFIRM_SIGN_IN_WITH_OTP) { // Show UI to collect OTP } // Then pass that OTP into the confirmSignIn API -result = Amplify.Auth.confirmSignIn("123456") - -// result.nextStep.signInStep should be "DONE" now +val confirmResult = Amplify.Auth.confirmSignIn( + challengeResponse = "123456" +) +// confirmResult.nextStep.signInStep should be "DONE" ``` ```java -// First confirm the challenge type -RxAmplify.Auth.confirmSignIn(AuthFactorType.SMS_OTP.getChallengeResponse()) - .subscribe( - result -> { - if (result.getNextStep().getSignInStep() == AuthSignInStep.CONFIRM_SIGN_IN_WITH_OTP) { - // Show UI to collect OTP - } - }, - error -> Log.e("AuthQuickstart", error.toString()) - ); +// Use options to specify the preferred first factor +AWSCognitoAuthSignInOptions options = AWSCognitoAuthSignInOptions.builder() + .authFlowType(AuthFlowType.USER_AUTH) + .preferredFirstFactor(AuthFactorType.SMS_OTP) // Sign in using SMS OTP + .build(); + +// Sign in the user +RxAmplify.Auth.signIn( + username, + null, // no password + options +).subscribe( + result -> { + if (result.getNextStep().getSignInStep() == AuthSignInStep.CONFIRM_SIGN_IN_WITH_OTP) { + // Show UI to collect OTP + } + }, + error -> Log.e("AuthQuickstart", error.toString()) +) // Then pass that OTP into the confirmSignIn API RxAmplify.Auth.confirmSignIn("123456") @@ -1389,17 +1418,23 @@ if (signInNextStep.signInStep === 'CONFIRM_SIGN_IN_WITH_EMAIL_CODE') { -To request an OTP code via email for authentication, you pass the `challengeResponse` for `AuthFactorType.EMAIL_OTP` to the `confirmSignIn` API. - -Amplify will respond appropriately to Cognito and return the challenge as the sign in next step: `CONFIRM_SIGN_IN_WITH_OTP_CODE`. You will call `confirmSignIn` again, this time with the OTP that your user provides. +Pass `EMAIL_OTP` as the `preferredFirstFactor` when calling the `signIn` API in order to initiate a passwordless authentication flow with Email OTP. ```java -// First confirm the challenge type -Amplify.Auth.confirmSignIn( - AuthFactorType.EMAIL_OTP.getChallengeResponse(), +// Use options to specify the preferred first factor +AWSCognitoAuthSignInOptions options = AWSCognitoAuthSignInOptions.builder() + .authFlowType(AuthFlowType.USER_AUTH) + .preferredFirstFactor(AuthFactorType.EMAIL_OTP) // Sign in using Email OTP + .build(); + +// Sign in the user +Amplify.Auth.signIn( + username, + null, // no password + options, result -> { if (result.getNextStep().getSignInStep() == AuthSignInStep.CONFIRM_SIGN_IN_WITH_OTP) { // Show UI to collect OTP @@ -1422,28 +1457,30 @@ Amplify.Auth.confirmSignIn( ```kotlin -// First confirm the challenge type -Amplify.Auth.confirmSignIn( - AuthFactorType.EMAIL_OTP.challengeResponse, - { result -> +// Use options to specify the preferred first factor +val options = AWSCognitoAuthSignInOptions.builder() + .authFlowType(AuthFlowType.USER_AUTH) + .preferredFirstFactor(AuthFactorType.EMAIL_OTP) // Sign in using Email OTP + .build() + +// Sign in the user +Amplify.Auth.signIn( + username, + null, // no password + options, + { result: AuthSignInResult -> if (result.nextStep.signInStep == AuthSignInStep.CONFIRM_SIGN_IN_WITH_OTP) { // Show UI to collect OTP } }, - { error -> - Log.e("AuthQuickstart", "Failed to sign in", error) - } + { error: AuthException -> Log.e("AuthQuickstart", error.toString()) } ) // Then pass that OTP into the confirmSignIn API Amplify.Auth.confirmSignIn( "123456", - { result -> - // result.nextStep.signInStep should be "DONE" now - }, - { error -> - Log.e("AuthQuickstart", "Failed to sign in", error) - } + { result: AuthSignInResult? -> }, + { error: AuthException -> Log.e("AuthQuickstart", error.toString()) } ) ``` @@ -1451,32 +1488,52 @@ Amplify.Auth.confirmSignIn( ```kotlin -// First confirm the challenge type -var result = Amplify.Auth.confirmSignIn(AuthFactorType.EMAIL_OTP.challengeResponse) +// Use options to specify the preferred first factor +val options = AWSCognitoAuthSignInOptions.builder() + .authFlowType(AuthFlowType.USER_AUTH) + .preferredFirstFactor(AuthFactorType.EMAIL_OTP) // Sign in using Email OTP + .build() + +// Sign in the user +val result = Amplify.Auth.signIn( + username = username, + password = null, + options = options +) if (result.nextStep.signInStep == AuthSignInStep.CONFIRM_SIGN_IN_WITH_OTP) { // Show UI to collect OTP } // Then pass that OTP into the confirmSignIn API -result = Amplify.Auth.confirmSignIn("123456") - -// result.nextStep.signInStep should be "DONE" now +val confirmResult = Amplify.Auth.confirmSignIn( + challengeResponse = "123456" +) +// confirmResult.nextStep.signInStep should be "DONE" ``` ```java -// First confirm the challenge type -RxAmplify.Auth.confirmSignIn(AuthFactorType.EMAIL_OTP.getChallengeResponse()) - .subscribe( - result -> { - if (result.getNextStep().getSignInStep() == AuthSignInStep.CONFIRM_SIGN_IN_WITH_OTP) { - // Show UI to collect OTP - } - }, - error -> Log.e("AuthQuickstart", error.toString()) - ); +// Use options to specify the preferred first factor +AWSCognitoAuthSignInOptions options = AWSCognitoAuthSignInOptions.builder() + .authFlowType(AuthFlowType.USER_AUTH) + .preferredFirstFactor(AuthFactorType.EMAIL_OTP) // Sign in using Email OTP + .build(); + +// Sign in the user +RxAmplify.Auth.signIn( + username, + null, // no password + options +).subscribe( + result -> { + if (result.getNextStep().getSignInStep() == AuthSignInStep.CONFIRM_SIGN_IN_WITH_OTP) { + // Show UI to collect OTP + } + }, + error -> Log.e("AuthQuickstart", error.toString()) +) // Then pass that OTP into the confirmSignIn API RxAmplify.Auth.confirmSignIn("123456") @@ -1491,8 +1548,6 @@ RxAmplify.Auth.confirmSignIn("123456") - - @@ -1597,28 +1652,35 @@ if (signInNextStep.signInStep === 'DONE') { -To sign in with WebAuthn, you pass the `challengeResponse` for `AuthFactorType.WEB_AUTHN` to the `confirmSignIn` API. Amplify will invoke Android's Credential Manager to retrieve a PassKey, and the user will be shown a system UI to authorize the PassKey access. This flow -completes without any additional interaction from your application, so there is only one `confirmSignIn` call needed for WebAuthn. +Pass `WEB_AUTHN` as the `preferredFirstFactor` in order to initiate the passwordless authentication flow using a WebAuthn credential. This flow +completes without any additional interaction from your application, so there is only one `Amplify.Auth` call needed for WebAuthn. + + +The user must have previously associated a credential to use this auth factor. To learn more, visit the [manage WebAuthn credentials page](/[platform]/build-a-backend/auth/manage-users/manage-webauthn-credentials/). + -Amplify requires an `Activity` reference to attach the PassKey UI to your Application's [Task](https://developer.android.com/guide/components/activities/tasks-and-back-stack) when using WebAuthn - if an `Activity` is not supplied then the UI will appear in a separate Task. For this reason, we strongly recommend passing the `callingActivity` option to both the `signIn` and `confirmSignIn` APIs if your application uses the `USER_AUTH` flow. +Amplify requires an `Activity` reference to attach the PassKey UI to your Application's [Task](https://developer.android.com/guide/components/activities/tasks-and-back-stack) when using WebAuthn - if an `Activity` is not supplied then the UI will appear in a separate Task. For this reason, we strongly recommend always passing the `callingActivity` option to both the `signIn` and `confirmSignIn` APIs if your application allows users to sign in with passkeys. ```java -// Pass the calling activity -AuthSignInOptions options = AWSCognitoAuthConfirmSignInOptions.builder() - .callingActivity(activity) +// Use options to specify the preferred first factor +AWSCognitoAuthSignInOptions options = AWSCognitoAuthSignInOptions.builder() + .authFlowType(AuthFlowType.USER_AUTH) + .callingActivity(callingActivity) + .preferredFirstFactor(AuthFactorType.WEB_AUTHN) // Sign in using WebAuthn .build(); -// Confirm WebAuthn as the challenge type -Amplify.Auth.confirmSignIn( - AuthFactorType.WEB_AUTHN.getChallengeResponse(), +// Sign in the user +Amplify.Auth.signIn( + username, + null, // no password options, result -> Log.i("AuthQuickStart", "Next sign in step: " + result.getNextStep()), - error -> Log.e("AuthQuickstart", "Failed to sign in", error) + error -> Log.e("AuthQuickstart", error.toString()) ); ``` @@ -1626,17 +1688,20 @@ Amplify.Auth.confirmSignIn( ```kotlin -// Pass the calling activity -val options = AWSCognitoAuthConfirmSignInOptions.builder() - .callingActivity(activity) +// Use options to specify the preferred first factor +val options = AWSCognitoAuthSignInOptions.builder() + .authFlowType(AuthFlowType.USER_AUTH) + .callingActivity(callingActivity) + .preferredFirstFactor(AuthFactorType.WEB_AUTHN) // Sign in using WebAuthn .build() -// Confirm WebAuthn as the challenge type -Amplify.Auth.confirmSignIn( - AuthFactorType.WEB_AUTHN.name, +// Sign in the user +Amplify.Auth.signIn( + username, + null, // no password options, - { result -> Log.i("AuthQuickStart", "Next sign in step: ${result.nextStep}") }, - { error -> Log.e("AuthQuickstart", "Failed to sign in", error) } + { result: AuthSignInResult -> Log.i("AuthQuickStart", "Next sign in step: ${result.nextStep}") }, + { error: AuthException -> Log.e("AuthQuickStart", error.toString()) } ) ``` @@ -1644,38 +1709,44 @@ Amplify.Auth.confirmSignIn( ```kotlin -// Pass the calling activity -val options = AWSCognitoAuthConfirmSignInOptions.builder() - .callingActivity(activity) +// Use options to specify the preferred first factor +val options = AWSCognitoAuthSignInOptions.builder() + .authFlowType(AuthFlowType.USER_AUTH) + .callingActivity(callingActivity) + .preferredFirstFactor(AuthFactorType.WEB_AUTHN) // Sign in using WebAuthn .build() -try { - // Confirm WebAuthn as the challenge type - var result = Amplify.Auth.confirmSignIn( - challengeResponse = AuthFactorType.WEB_AUTHN.challengeResponse, - options = options - ) - Log.i("AuthQuickStart", "Next sign in step: ${result.nextStep}") -} catch (error: AuthException) { - Log.e("AuthQuickstart", "Failed to sign in", error) -} +// Sign in the user +val result = Amplify.Auth.signIn( + username = username, + password = null, + options = options +) + +// result.nextStep.signInStep should be "DONE" if use granted access to the passkey +// NOTE: `signIn` will throw a UserCancelledException if user dismissed the passkey UI ``` ```java -// Pass the calling activity -AuthSignInOptions options = AWSCognitoAuthConfirmSignInOptions.builder() - .callingActivity(activity) +// Use options to specify the preferred first factor +AWSCognitoAuthSignInOptions options = AWSCognitoAuthSignInOptions.builder() + .authFlowType(AuthFlowType.USER_AUTH) + .callingActivity(callingActivity) + .preferredFirstFactor(AuthFactorType.WEB_AUTHN) // Sign in using WebAuthn .build(); -// Confirm WebAuthn as the challenge type -RxAmplify.Auth.confirmSignIn(AuthFactorType.WEB_AUTHN.getChallengeResponse(), options) - .subscribe( - result -> Log.i("AuthQuickStart", "Next sign in step: " + result.getNextStep()), - error -> Log.e("AuthQuickstart", "Failed to sign in", error) - ); +// Sign in the user +RxAmplify.Auth.signIn( + username, + null, // no password + options +).subscribe( + result -> Log.i("AuthQuickStart", "Next sign in step: " + result.getNextStep()), + error -> Log.e("AuthQuickstart", error.toString()) +) ``` @@ -1683,7 +1754,7 @@ RxAmplify.Auth.confirmSignIn(AuthFactorType.WEB_AUTHN.getChallengeResponse(), op Using WebAuthn sign in may result in a number of possible exception types. -- `UserCancelledException` - If the user declines to authorize access to the PassKey in the system UI. You can retry the WebAuthn flow by invoking `confirmSignIn` again, or restart the `signIn` process to select a different `AuthFactorType`. +- `UserCancelledException` - If the user declines to authorize access to the passkey in the system UI. You can retry the WebAuthn flow by invoking `confirmSignIn` again, or restart the `signIn` process to select a different `AuthFactorType`. - `WebAuthnNotEnabledException` - This indicates WebAuthn is not enabled in your user pool. - `WebAuthnNotSupportedException` - This indicates WebAuthn is not supported on the user's device. - `WebAuthnRpMismatchException` - This indicates there is a problem with the `assetlinks.json` file deployed to your relying party. @@ -1794,23 +1865,18 @@ if (confirmSignInNextStep.signInStep === 'DONE') { ```java -// First confirm the challenge type -Amplify.Auth.confirmSignIn( - AuthFactorType.PASSWORD.getChallengeResponse(), // or PASSWORD_SRP - result -> { - if (result.getNextStep().getSignInStep() == AuthSignInStep.CONFIRM_SIGN_IN_WITH_PASSWORD) { - // Show UI to collect password - } - }, - error -> Log.e("AuthQuickstart", error.toString()) -); +// Use options to specify the preferred first factor +AWSCognitoAuthSignInOptions options = AWSCognitoAuthSignInOptions.builder() + .authFlowType(AuthFlowType.USER_AUTH) + .preferredFirstFactor(AuthFactorType.PASSWORD) // Sign in using Password + .build(); -// Then pass that password into the confirmSignIn API -Amplify.Auth.confirmSignIn( - "password", - result -> { - // result.getNextStep().getSignInStep() should be "DONE" now - }, +// Sign in the user +Amplify.Auth.signIn( + username, + password, // supply the password if preferredFirstFactor is PASSWORD or PASSWORD_SRP + options, + result -> Log.i("AuthQuickStart", "Next sign in step: " + result.getNextStep()), error -> Log.e("AuthQuickstart", error.toString()) ); ``` @@ -1819,28 +1885,19 @@ Amplify.Auth.confirmSignIn( ```kotlin -// First confirm the challenge type -Amplify.Auth.confirmSignIn( - AuthFactorType.PASSWORD.challengeResponse, // or PASSWORD_SRP - { result -> - if (result.nextStep.signInStep == AuthSignInStep.CONFIRM_SIGN_IN_WITH_PASSWORD) { - // Show UI to collect password - } - }, - { error -> - Log.e("AuthQuickstart", "Failed to sign in", error) - } -) +// Use options to specify the preferred first factor +val options = AWSCognitoAuthSignInOptions.builder() + .authFlowType(AuthFlowType.USER_AUTH) + .preferredFirstFactor(AuthFactorType.PASSWORD) // Sign in using Password + .build() -// Then pass that password into the confirmSignIn API -Amplify.Auth.confirmSignIn( - "password", - { result -> - // result.nextStep.signInStep should be "DONE" now - }, - { error -> - Log.e("AuthQuickstart", "Failed to sign in", error) - } +// Sign in the user +Amplify.Auth.signIn( + username, + password, // supply the password if preferredFirstFactor is PASSWORD or PASSWORD_SRP + options, + { result: AuthSignInResult -> Log.i("AuthQuickStart", "Next sign in step: ${result.nextStep}") }, + { error: AuthException -> Log.e("AuthQuickstart", error.toString()) } ) ``` @@ -1848,41 +1905,41 @@ Amplify.Auth.confirmSignIn( ```kotlin -// First confirm the challenge type -var result = Amplify.Auth.confirmSignIn(AuthFactorType.PASSWORD.challengeResponse) // or PASSWORD_SRP -if (result.nextStep.signInStep == AuthSignInStep.CONFIRM_SIGN_IN_WITH_PASSWORD) { - // Show UI to collect password -} +// Use options to specify the preferred first factor +val options = AWSCognitoAuthSignInOptions.builder() + .authFlowType(AuthFlowType.USER_AUTH) + .preferredFirstFactor(AuthFactorType.PASSWORD) // Sign in using Password + .build() -// Then pass that password into the confirmSignIn API -result = Amplify.Auth.confirmSignIn("password") +// Sign in the user +val result = Amplify.Auth.signIn( + username = username, + password = password, // supply the password if preferredFirstFactor is PASSWORD or PASSWORD_SRP + options = options +) -// result.nextStep.signInStep should be "DONE" now +// result.nextStep.signInStep should be "DONE" ``` ```java -// First confirm the challenge type -RxAmplify.Auth.confirmSignIn(AuthFactorType.PASSWORD.getChallengeResponse()) // or PASSWORD_SRP - .subscribe( - result -> { - if (result.getNextStep().getSignInStep() == AuthSignInStep.CONFIRM_SIGN_IN_WITH_PASSWORD) { - // Show UI to collect password - } - }, - error -> Log.e("AuthQuickstart", error.toString()) - ); +// Use options to specify the preferred first factor +AWSCognitoAuthSignInOptions options = AWSCognitoAuthSignInOptions.builder() + .authFlowType(AuthFlowType.USER_AUTH) + .preferredFirstFactor(AuthFactorType.Password) // Sign in using Password + .build(); -// Then pass that password into the confirmSignIn API -RxAmplify.Auth.confirmSignIn("password") - .subscribe( - result -> { - // result.getNextStep().getSignInStep() should be "DONE" now - }, - error -> Log.e("AuthQuickstart", error.toString()) - ); +// Sign in the user +RxAmplify.Auth.signIn( + username, + password, // supply the password if preferredFirstFactor is PASSWORD or PASSWORD_SRP + options +).subscribe( + result -> Log.i("AuthQuickStart", "Next sign in step: " + result.getNextStep()), + error -> Log.e("AuthQuickstart", error.toString()) +) ``` @@ -1893,7 +1950,8 @@ RxAmplify.Auth.confirmSignIn("password") ### First Factor Selection -Omit the `preferredChallenge` parameter to discover what first factors are available for a given user. +Omit the `preferredChallenge` parameter to discover which first factors are available for a given user. This is useful to allow +users to choose how they would like to sign in. The `confirmSignIn` API can then be used to select a challenge and initiate the associated authentication flow. @@ -1927,13 +1985,13 @@ if ( ```java -// Retrieve the authentication factors by calling .availableFactors -AWSCognitoAuthSignInOptions options = - AWSCognitoAuthSignInOptions - .builder() - .authFlowType(AuthFlowType.USER_AUTH) - .callingActivity(callingActivity) - .build(); +// Omit preferredFirstFactor. If the user has more than one factor available then +// the next step will be CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION. +AuthSignInOptions options = AWSCognitoAuthSignInOptions.builder() + .authFlowType(AuthFlowType.USER_AUTH) + .build(); + +// Sign in the user Amplify.Auth.signIn( "hello@example.com", null, @@ -1948,32 +2006,72 @@ Amplify.Auth.signIn( }, error -> Log.e("AuthQuickstart", error.toString()) ); + +// Select SMS OTP for sign in +Amplify.Auth.confirmSignIn( + AuthFactorType.SMS_OTP.getChallengeResponse(), + result -> { + if (result.getNextStep().getSignInStep() == AuthSignInStep.CONFIRM_SIGN_IN_WITH_OTP) { + // Show UI to collect OTP + } + }, + error -> Log.e("AuthQuickstart", error.toString()) +); + +// Then pass that OTP into the confirmSignIn API +Amplify.Auth.confirmSignIn( + "123456", + result -> { + // result.getNextStep().getSignInStep() should be "DONE" now + }, + error -> Log.e("AuthQuickstart", error.toString()) +); ``` ```kotlin -// Retrieve the authentication factors by calling .availableFactors -val options = AWSCognitoAuthSignInOptions.builder() +// Omit preferredFirstFactor. If the user has more than one factor available then +// the next step will be CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION. +val options: AuthSignInOptions = AWSCognitoAuthSignInOptions.builder() .authFlowType(AuthFlowType.USER_AUTH) - .callingActivity(callingActivity) .build() + +// Sign in the user Amplify.Auth.signIn( "hello@example.com", null, options, - { result -> + { result: AuthSignInResult -> if (result.nextStep.signInStep == AuthSignInStep.CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION) { Log.i( "AuthQuickstart", - "Available factors for this user: ${result.nextStep.availableFactors}" + "Available authentication factors for this user: " + result.nextStep.availableFactors ) } }, - { error -> - Log.e("AuthQuickstart", "Failed to sign in", error) - } + { error: AuthException -> Log.e("AuthQuickstart", error.toString()) } +) + +// Select SMS OTP for sign in +Amplify.Auth.confirmSignIn( + AuthFactorType.SMS_OTP.getChallengeResponse(), + { result: AuthSignInResult -> + if (result.nextStep.signInStep == AuthSignInStep.CONFIRM_SIGN_IN_WITH_OTP) { + // Show UI to collect OTP + } + }, + { error: AuthException -> Log.e("AuthQuickstart", error.toString()) } +) + +// Then pass that OTP into the confirmSignIn API +Amplify.Auth.confirmSignIn( + "123456", + { result: AuthSignInResult? -> + // result.nextStep.signInStep should be "DONE" now + }, + { error: AuthException -> Log.e("AuthQuickstart", error.toString()) } ) ``` @@ -1981,51 +2079,85 @@ Amplify.Auth.signIn( ```kotlin -try { - // Retrieve the authentication factors by calling .availableFactors - val options = AWSCognitoAuthSignInOptions.builder() - .authFlowType(AuthFlowType.USER_AUTH) - .callingActivity(callingActivity) - .build() - val result = Amplify.Auth.signIn( - username = "hello@example.com", - password = null, - options = options +// Omit preferredFirstFactor. If the user has more than one factor available then +// the next step will be CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION. +val options: AuthSignInOptions = AWSCognitoAuthSignInOptions.builder() + .authFlowType(AuthFlowType.USER_AUTH) + .build() + +// Sign in the user +val result = Amplify.Auth.signIn( + username = "hello@example.com", + password = null, + options = options +) + +if (result.nextStep.signInStep == AuthSignInStep.CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION) { + Log.i( + "AuthQuickStart", + "Available authentication factors for this user: ${result.nextStep.availableFactors}" ) - if (result.nextStep.signInStep == AuthSignInStep.CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION) { - Log.i( - "AuthQuickstart", - "Available factors for this user: ${result.nextStep.availableFactors}" - ) - } -} catch (error: AuthException) { - Log.e("AuthQuickstart", "Sign in failed", error) } + +// Select SMS OTP for sign in +val selectFactorResult = Amplify.Auth.confirmSignIn(challengeResponse = AuthFactorType.SMS_OTP.challengeResponse) + +if (result.nextStep.signInStep == AuthSignInStep.CONFIRM_SIGN_IN_WITH_OTP) { + // Show UI to collect OTP +} + +// Then pass that OTP into the confirmSignIn API +val confirmResult = Amplify.Auth.confirmSignIn(challengeResponse = "123456") + +// confirmResult.nextStep.signInStep should be "DONE" now ``` ```java -// Retrieve the authentication factors by calling .availableFactors -AWSCognitoAuthSignInOptions options = - AWSCognitoAuthSignInOptions - .builder() - .authFlowType(AuthFlowType.USER_AUTH) - .callingActivity(callingActivity) - .build(); -RxAmplify.Auth.signIn("hello@example.com", null, options) +// Omit preferredFirstFactor. If the user has more than one factor available then +// the next step will be CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION. +AWSCognitoAuthSignInOptions options = AWSCognitoAuthSignInOptions.builder() + .authFlowType(AuthFlowType.USER_AUTH) + .build(); + +// Sign in the user +RxAmplify.Auth.signIn( + username, + null, // no password + options +).subscribe( + result -> { + if (result.getNextStep().getSignInStep() == AuthSignInStep.CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION) { + Log.i( + "AuthQuickstart", + "Available authentication factors for this user: " + result.getNextStep().getAvailableFactors() + ); + } + }, + error -> Log.e("AuthQuickstart", error.toString()) +) + +// Select SMS OTP for sign in +RxAmplify.Auth.confirmSignIn(AuthFactorType.SMS_OTP.getChallengeResponse()) .subscribe( result -> { - if (result.getNextStep().getSignInStep() == AuthSignInStep.CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION) { - Log.i( - "AuthQuickstart", - "Available authentication factors for this user: " + result.getNextStep().getAvailableFactors() - ); + if (result.getNextStep().getSignInStep() == AuthSignInStep.CONFIRM_SIGN_IN_WITH_OTP) { + // Show UI to collect OTP } }, error -> Log.e("AuthQuickstart", error.toString()) ); + +// Then pass that OTP into the confirmSignIn API +RxAmplify.Auth.confirmSignIn("123456") + .subscribe( + result -> { + // result.getNextStep().getSignInStep() should be "DONE" now + }, + error -> Log.e("AuthQuickstart", error.toString()) + ); ``` diff --git a/src/pages/[platform]/build-a-backend/auth/manage-users/manage-webauthn-credentials/index.mdx b/src/pages/[platform]/build-a-backend/auth/manage-users/manage-webauthn-credentials/index.mdx index af358d68c50..644b40d2bb5 100644 --- a/src/pages/[platform]/build-a-backend/auth/manage-users/manage-webauthn-credentials/index.mdx +++ b/src/pages/[platform]/build-a-backend/auth/manage-users/manage-webauthn-credentials/index.mdx @@ -35,9 +35,17 @@ WebAuthn registration and authentication are not currently supported on React Na -Amplify Auth enables your users to associate, keep track of, and delete passkeys. +Amplify Auth uses passkeys as the credential mechanism for WebAuthn. The following APIs allow users to register, keep track of, and delete the passkeys associated with their Cognito account. -## Associate WebAuthN credentials +[Learn more about using passkeys with Amplify](/[platform]/build-a-backend/auth/concepts/passwordless/#webauthn-passkey). + +## Associate WebAuthn credentials + + + +Registering a passkey is supported on Android 9 (API level 28) and above. + + Note that users must be authenticated to register a passkey. That also means users cannot create a passkey during sign up; consequently, they must have at least one other first factor authentication mechanism associated with their account to use WebAuthn. @@ -62,7 +70,7 @@ await associateWebAuthnCredential(); Amplify.Auth.associateWebAuthnCredential( activity, () -> Log.i("AuthQuickstart", "Associated credential"), - error -> Log.e("AuthQuickstart", "Failed to register credential", error) + error -> Log.e("AuthQuickstart", "Failed to associate credential", error) ); ``` @@ -73,7 +81,7 @@ Amplify.Auth.associateWebAuthnCredential( Amplify.Auth.associateWebAuthnCredential( activity, { Log.i("AuthQuickstart", "Associated credential") }, - { Log.e("AuthQuickstart", "Failed to register credential", error) } + { Log.e("AuthQuickstart", "Failed to associate credential", error) } ) ``` @@ -103,7 +111,7 @@ RxAmplify.Auth.associateWebAuthnCredential(activity) -You must supply an `Activity` instance so that Amplify can display the PassKey UI in your Application's [Task](https://developer.android.com/guide/components/activities/tasks-and-back-stack). +You must supply an `Activity` instance so that Amplify can display the PassKey UI in your application's [Task](https://developer.android.com/guide/components/activities/tasks-and-back-stack). @@ -145,7 +153,7 @@ func associateWebAuthNCredentials() -> AnyCancellable { The user will be prompted to register a passkey using their local authenticator. Amplify will then associate that passkey with Cognito. -## List WebAuthN credentials +## List WebAuthn credentials You can list registered passkeys using the following API: @@ -301,7 +309,7 @@ RxAmplify.Auth.listWebAuthnCredentials() -## Delete WebAuthN credentials +## Delete WebAuthn credentials You can delete a passkey with the following API: diff --git a/src/pages/[platform]/build-a-backend/auth/modify-resources-with-cdk/index.mdx b/src/pages/[platform]/build-a-backend/auth/modify-resources-with-cdk/index.mdx index df7143cec42..b988c638ea0 100644 --- a/src/pages/[platform]/build-a-backend/auth/modify-resources-with-cdk/index.mdx +++ b/src/pages/[platform]/build-a-backend/auth/modify-resources-with-cdk/index.mdx @@ -121,7 +121,9 @@ cfnUserPool.enabledMfas = [...(cfnUserPool.enabledMfas || []), "EMAIL_OTP"] ### Override Cognito UserPool to enable passwordless sign-in methods -You can modify the underlying Cognito user pool resource to enable sign in with passwordless methods. [Learn more about passwordless sign-in methods](/[platform]/build-a-backend/auth/concepts/passwordless/). +You can modify the underlying Cognito user pool resource to enable sign in with passwordless methods. [Learn more about passwordless sign-in methods](/[platform]/build-a-backend/auth/concepts/passwordless/). + +You can also read more about passwordless authentication flows are implemented in the [Cognito documentation](https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-authentication-flow-methods.html). ```ts title="amplify/backend.ts" import { defineBackend } from "@aws-amplify/backend" @@ -134,17 +136,20 @@ const backend = defineBackend({ const { cfnResources } = backend.auth.resources; const { cfnUserPool, cfnUserPoolClient } = cfnResources; +// Specify which authentication factors you want to allow with USER_AUTH cfnUserPool.addPropertyOverride( 'Policies.SignInPolicy.AllowedFirstAuthFactors', ['PASSWORD', 'WEB_AUTHN', 'EMAIL_OTP', 'SMS_OTP'] ); +// The USER_AUTH flow is used for passwordless sign in cfnUserPoolClient.explicitAuthFlows = [ 'ALLOW_REFRESH_TOKEN_AUTH', 'ALLOW_USER_AUTH' ]; /* Needed for WebAuthn */ +// The WebAuthnRelyingPartyID is the domain of your relying party, e.g. "example.mydomain.com" cfnUserPool.addPropertyOverride('WebAuthnRelyingPartyID', ''); cfnUserPool.addPropertyOverride('WebAuthnUserVerification', 'preferred'); ``` From 027e2cf722b752994d92f476a9ee131a5a70221d Mon Sep 17 00:00:00 2001 From: Matt Creaser Date: Wed, 11 Jun 2025 12:56:02 -0300 Subject: [PATCH 5/7] Update managing credentials blurb --- .../build-a-backend/auth/concepts/passwordless/index.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/[platform]/build-a-backend/auth/concepts/passwordless/index.mdx b/src/pages/[platform]/build-a-backend/auth/concepts/passwordless/index.mdx index f256364986e..8cd22a9f81a 100644 --- a/src/pages/[platform]/build-a-backend/auth/concepts/passwordless/index.mdx +++ b/src/pages/[platform]/build-a-backend/auth/concepts/passwordless/index.mdx @@ -154,6 +154,6 @@ Using passkeys with Amplify requires following these steps: ### Managing credentials -Passwordless authentication with WebAuthn requires associating one or more credentials with the user's Amazon Cognito account. Amplify provides APIs that integrate with each platform's authenticator mechanism to easily create, view, and delete these credential assocations. +Passwordless authentication with WebAuthn requires associating one or more credentials with the user's Amazon Cognito account. Amplify provides APIs that integrate with each platform's local authenticator to easily create, view, and delete these credential assocations. [Learn more about managing WebAuthn credentials](/[platform]/build-a-backend/auth/manage-users/manage-webauthn-credentials). From 1936f45b35949b28ec7f715daa222b45d091dae3 Mon Sep 17 00:00:00 2001 From: Matt Creaser Date: Wed, 11 Jun 2025 13:10:27 -0300 Subject: [PATCH 6/7] Fix spelling check failures --- .../build-a-backend/auth/concepts/passwordless/index.mdx | 2 +- .../build-a-backend/auth/modify-resources-with-cdk/index.mdx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pages/[platform]/build-a-backend/auth/concepts/passwordless/index.mdx b/src/pages/[platform]/build-a-backend/auth/concepts/passwordless/index.mdx index 8cd22a9f81a..5b0aba2f516 100644 --- a/src/pages/[platform]/build-a-backend/auth/concepts/passwordless/index.mdx +++ b/src/pages/[platform]/build-a-backend/auth/concepts/passwordless/index.mdx @@ -154,6 +154,6 @@ Using passkeys with Amplify requires following these steps: ### Managing credentials -Passwordless authentication with WebAuthn requires associating one or more credentials with the user's Amazon Cognito account. Amplify provides APIs that integrate with each platform's local authenticator to easily create, view, and delete these credential assocations. +Passwordless authentication with WebAuthn requires associating one or more credentials with the user's Amazon Cognito account. Amplify provides APIs that integrate with each platform's local authenticator to easily create, view, and delete these credential associations. [Learn more about managing WebAuthn credentials](/[platform]/build-a-backend/auth/manage-users/manage-webauthn-credentials). diff --git a/src/pages/[platform]/build-a-backend/auth/modify-resources-with-cdk/index.mdx b/src/pages/[platform]/build-a-backend/auth/modify-resources-with-cdk/index.mdx index b988c638ea0..6534247ad93 100644 --- a/src/pages/[platform]/build-a-backend/auth/modify-resources-with-cdk/index.mdx +++ b/src/pages/[platform]/build-a-backend/auth/modify-resources-with-cdk/index.mdx @@ -123,7 +123,7 @@ cfnUserPool.enabledMfas = [...(cfnUserPool.enabledMfas || []), "EMAIL_OTP"] You can modify the underlying Cognito user pool resource to enable sign in with passwordless methods. [Learn more about passwordless sign-in methods](/[platform]/build-a-backend/auth/concepts/passwordless/). -You can also read more about passwordless authentication flows are implemented in the [Cognito documentation](https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-authentication-flow-methods.html). +You can also read more about how passwordless authentication flows are implemented in the [Cognito documentation](https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-authentication-flow-methods.html). ```ts title="amplify/backend.ts" import { defineBackend } from "@aws-amplify/backend" @@ -149,7 +149,7 @@ cfnUserPoolClient.explicitAuthFlows = [ ]; /* Needed for WebAuthn */ -// The WebAuthnRelyingPartyID is the domain of your relying party, e.g. "example.mydomain.com" +// The WebAuthnRelyingPartyID is the domain of your relying party, e.g. "example.domain.com" cfnUserPool.addPropertyOverride('WebAuthnRelyingPartyID', ''); cfnUserPool.addPropertyOverride('WebAuthnUserVerification', 'preferred'); ``` From 5441a236025e6bdd641f66292e3f2be0c574a442 Mon Sep 17 00:00:00 2001 From: Matt Creaser Date: Thu, 12 Jun 2025 17:05:08 -0400 Subject: [PATCH 7/7] Add Swift code samples for password and for selecting preferred challenge --- .../connect-your-frontend/sign-in/index.mdx | 232 ++++++++++++++++-- 1 file changed, 211 insertions(+), 21 deletions(-) diff --git a/src/pages/[platform]/build-a-backend/auth/connect-your-frontend/sign-in/index.mdx b/src/pages/[platform]/build-a-backend/auth/connect-your-frontend/sign-in/index.mdx index 25580fd5653..5f97f5e3e9c 100644 --- a/src/pages/[platform]/build-a-backend/auth/connect-your-frontend/sign-in/index.mdx +++ b/src/pages/[platform]/build-a-backend/auth/connect-your-frontend/sign-in/index.mdx @@ -1310,6 +1310,8 @@ RxAmplify.Auth.confirmSignIn("123456") +Pass `smsOTP` as the `preferredFirstFactor` when calling the `signIn` API in order to initiate a passwordless authentication flow with SMS OTP. + @@ -1551,6 +1553,9 @@ RxAmplify.Auth.confirmSignIn("123456") +Pass `emailOTP` as the `preferredFirstFactor` when calling the `signIn` API in order to initiate a passwordless authentication flow with Email OTP. + + @@ -1761,13 +1766,14 @@ Using WebAuthn sign in may result in a number of possible exception types. - `WebAuthnFailedException` - This exception is used for other errors that may occur with WebAuthn. Inspect the `cause` to determine the best course of action. - {/* */} +Pass `webAuthn` as the `preferredFirstFactor` in order to initiate the passwordless authentication flow using a WebAuthn credential. + @@ -1834,15 +1840,13 @@ func signIn(username: String) async { - ### Password -Pass either `PASSWORD` or `PASSWORD_SRP` as the `preferredChallenge` in order to initiate a traditional password based authentication flow. + - +Pass either `PASSWORD` or `PASSWORD_SRP` as the `preferredChallenge` in order to initiate a traditional password based authentication flow. - ```ts const { nextStep: signInNextStep } = await signIn({ username: 'hello@example.com', @@ -1861,6 +1865,9 @@ if (confirmSignInNextStep.signInStep === 'DONE') { +Pass either `PASSWORD` or `PASSWORD_SRP` as the `preferredFirstFactor` in order to initiate a traditional password based authentication flow. + + @@ -1945,18 +1952,69 @@ RxAmplify.Auth.signIn( + + + +Pass either `password` or `passwordSRP` as the `preferredFirstFactor` in order to initiate a traditional password based authentication flow. + + + + +```swift +// sign in with `password` as preferred factor +func signIn(username: String) async { + do { + let pluginOptions = AWSAuthSignInOptions( + authFlowType: .userAuth(preferredFirstFactor: .password)) + let signInResult = try await Amplify.Auth.signIn( + username: username, + options: .init(pluginOptions: pluginOptions)) + print("Sign in succeeded. Next step: \(signInResult.nextStep)") + } catch let error as AuthError { + print("Sign in failed \(error)") + } catch { + print("Unexpected error: \(error)") + } +} + +``` + + + + +```swift +// sign in with `password` as preferred factor +func signIn(username: String) async { + Amplify.Publisher.create { + let pluginOptions = AWSAuthSignInOptions( + authFlowType: .userAuth(preferredFirstFactor: .password)) + try await Amplify.Auth.signIn( + username: username, + options: .init(pluginOptions: pluginOptions)) + }.sink { + if case let .failure(authError) = $0 { + print("Sign in failed \(authError)") + } + } + receiveValue: { signInResult in + print("Sign in succeeded. Next step: \(signInResult.nextStep)") + } +} +``` + + + - ### First Factor Selection + + Omit the `preferredChallenge` parameter to discover which first factors are available for a given user. This is useful to allow users to choose how they would like to sign in. The `confirmSignIn` API can then be used to select a challenge and initiate the associated authentication flow. - - ```ts const { nextStep: signInNextStep } = await signIn({ username: '+15551234567', @@ -1981,6 +2039,12 @@ if ( + +Omit the `preferredFirstFactor` option to discover which first factors are available for a given user. This is useful to allow +users to choose how they would like to sign in. + +The `confirmSignIn` API can then be used to select a challenge and initiate the associated authentication flow. + @@ -1991,7 +2055,7 @@ AuthSignInOptions options = AWSCognitoAuthSignInOptions.builder() .authFlowType(AuthFlowType.USER_AUTH) .build(); -// Sign in the user +// Step 1: Sign in the user Amplify.Auth.signIn( "hello@example.com", null, @@ -2007,18 +2071,22 @@ Amplify.Auth.signIn( error -> Log.e("AuthQuickstart", error.toString()) ); -// Select SMS OTP for sign in +// Step 2: Select SMS OTP for sign in Amplify.Auth.confirmSignIn( AuthFactorType.SMS_OTP.getChallengeResponse(), result -> { if (result.getNextStep().getSignInStep() == AuthSignInStep.CONFIRM_SIGN_IN_WITH_OTP) { + Log.i( + "AuthQuickStart", + "OTP code sent to " + result.getNextStep().getCodeDeliveryDetails() + ) // Show UI to collect OTP } }, error -> Log.e("AuthQuickstart", error.toString()) ); -// Then pass that OTP into the confirmSignIn API +// Step 3: Then pass that OTP into the confirmSignIn API Amplify.Auth.confirmSignIn( "123456", result -> { @@ -2038,7 +2106,7 @@ val options: AuthSignInOptions = AWSCognitoAuthSignInOptions.builder() .authFlowType(AuthFlowType.USER_AUTH) .build() -// Sign in the user +// Step 1: Sign in the user Amplify.Auth.signIn( "hello@example.com", null, @@ -2047,25 +2115,29 @@ Amplify.Auth.signIn( if (result.nextStep.signInStep == AuthSignInStep.CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION) { Log.i( "AuthQuickstart", - "Available authentication factors for this user: " + result.nextStep.availableFactors + "Available authentication factors for this user: ${result.nextStep.availableFactors}" ) } }, { error: AuthException -> Log.e("AuthQuickstart", error.toString()) } ) -// Select SMS OTP for sign in +// Step 2: Select SMS OTP for sign in Amplify.Auth.confirmSignIn( AuthFactorType.SMS_OTP.getChallengeResponse(), { result: AuthSignInResult -> if (result.nextStep.signInStep == AuthSignInStep.CONFIRM_SIGN_IN_WITH_OTP) { + Log.i( + "AuthQuickStart", + "OTP code sent to ${result.nextStep.codeDeliveryDetails}" + ) // Show UI to collect OTP } }, { error: AuthException -> Log.e("AuthQuickstart", error.toString()) } ) -// Then pass that OTP into the confirmSignIn API +// Step 3: Then pass that OTP into the confirmSignIn API Amplify.Auth.confirmSignIn( "123456", { result: AuthSignInResult? -> @@ -2085,7 +2157,7 @@ val options: AuthSignInOptions = AWSCognitoAuthSignInOptions.builder() .authFlowType(AuthFlowType.USER_AUTH) .build() -// Sign in the user +// Step 1: Sign in the user val result = Amplify.Auth.signIn( username = "hello@example.com", password = null, @@ -2099,14 +2171,18 @@ if (result.nextStep.signInStep == AuthSignInStep.CONTINUE_SIGN_IN_WITH_FIRST_FAC ) } -// Select SMS OTP for sign in +// Step 2: Select SMS OTP for sign in val selectFactorResult = Amplify.Auth.confirmSignIn(challengeResponse = AuthFactorType.SMS_OTP.challengeResponse) if (result.nextStep.signInStep == AuthSignInStep.CONFIRM_SIGN_IN_WITH_OTP) { + Log.i( + "AuthQuickStart", + "OTP code sent to ${result.nextStep.codeDeliveryDetails}" + ) // Show UI to collect OTP } -// Then pass that OTP into the confirmSignIn API +// Step 3: Then pass that OTP into the confirmSignIn API val confirmResult = Amplify.Auth.confirmSignIn(challengeResponse = "123456") // confirmResult.nextStep.signInStep should be "DONE" now @@ -2122,7 +2198,7 @@ AWSCognitoAuthSignInOptions options = AWSCognitoAuthSignInOptions.builder() .authFlowType(AuthFlowType.USER_AUTH) .build(); -// Sign in the user +// Step 1: Sign in the user RxAmplify.Auth.signIn( username, null, // no password @@ -2139,18 +2215,22 @@ RxAmplify.Auth.signIn( error -> Log.e("AuthQuickstart", error.toString()) ) -// Select SMS OTP for sign in +// Step 2: Select SMS OTP for sign in RxAmplify.Auth.confirmSignIn(AuthFactorType.SMS_OTP.getChallengeResponse()) .subscribe( result -> { if (result.getNextStep().getSignInStep() == AuthSignInStep.CONFIRM_SIGN_IN_WITH_OTP) { + Log.i( + "AuthQuickStart", + "OTP code sent to " + result.getNextStep().getCodeDeliveryDetails() + ) // Show UI to collect OTP } }, error -> Log.e("AuthQuickstart", error.toString()) ); -// Then pass that OTP into the confirmSignIn API +// Step 3: Then pass that OTP into the confirmSignIn API RxAmplify.Auth.confirmSignIn("123456") .subscribe( result -> { @@ -2164,7 +2244,117 @@ RxAmplify.Auth.confirmSignIn("123456") + + +Omit the `preferredFirstFactor` parameter to discover which first factors are available for a given user. This is useful to allow +users to choose how they would like to sign in. + +The `confirmSignIn` API can then be used to select a challenge and initiate the associated authentication flow. + + + + +```swift +// Step 1: Initiate UserAuth Sign-In +let pluginOptions = AWSAuthSignInOptions(authFlowType: .userAuth) +let signInResult = try await Amplify.Auth.signIn( + username: "user@example.com", + options: .init(pluginOptions: pluginOptions) +) + +switch signInResult.nextStep { +case .continueSignInWithFirstFactorSelection(let availableFactors): + print("Available factors to select: \(availableFactors)") + // Prompt the user to select a first factor +default: + break +} +// Step 2: Select Authentication Factor +let confirmSignInResult = try await Amplify.Auth.confirmSignIn( + challengeResponse: AuthFactorType.emailOTP.challengeResponse +) + +switch confirmSignInResult.nextStep { +case .confirmSignInWithOTP(let deliveryDetails): + print("Delivery details: \(deliveryDetails)") + // Prompt the user to enter email OTP code received +default: + break +} + +// Step 3: Complete Sign-In with OTP Code +let finalSignInResult = try await Amplify.Auth.confirmSignIn( + challengeResponse: "" +) + +if case .done = finalSignInResult.nextStep { + print("Login successful") +} +``` + + + + +```swift +func signInWithUserAuth(username: String, getOTP: @escaping () async -> String) -> AnyCancellable { + Amplify.Publisher.create { + // Step 1: Initiate UserAuth Sign-In + let pluginOptions = AWSAuthSignInOptions(authFlowType: .userAuth) + let signInResult = try await Amplify.Auth.signIn( + username: username, + options: .init(pluginOptions: pluginOptions) + ) + + // Step 2: Handle factor selection + if case .continueSignInWithFirstFactorSelection(let availableFactors) = signInResult.nextStep { + print("Available factors to select: \(availableFactors)") + + // For this example, we select emailOTP. You could prompt the user here. + let confirmSignInResult = try await Amplify.Auth.confirmSignIn( + challengeResponse: AuthFactorType.emailOTP.challengeResponse + ) + + // Step 3: Handle OTP delivery + if case .confirmSignInWithOTP(let deliveryDetails) = confirmSignInResult.nextStep { + print("Delivery details: \(deliveryDetails)") + + // Prompt user for OTP code (async closure) + let code = await getOTP() + + // Step 4: Complete sign-in with OTP code + let finalSignInResult = try await Amplify.Auth.confirmSignIn( + challengeResponse: code + ) + + return finalSignInResult + } else { + // Handle other next steps if needed + return confirmSignInResult + } + } else { + // Handle other next steps or immediate sign-in + return signInResult + } + } + .sink( + receiveCompletion: { completion in + if case let .failure(authError) = completion { + print("Sign in failed: \(authError)") + } + }, + receiveValue: { result in + if result.isSignedIn || (result.nextStep == .done) { + print("Sign in succeeded") + } else { + print("Next step: \(result.nextStep)") + } + } + ) +} +``` + +