diff --git a/cspell.json b/cspell.json index 3bcd96969a0..8aa9138f087 100644 --- a/cspell.json +++ b/cspell.json @@ -1614,6 +1614,7 @@ "ampx", "autodetection", "jamba", + "webauthn", "knowledgebases", "rehype" ], diff --git a/src/directory/directory.mjs b/src/directory/directory.mjs index 39a36c3dd32..269af6f34ad 100644 --- a/src/directory/directory.mjs +++ b/src/directory/directory.mjs @@ -73,6 +73,9 @@ export const directory = { { path: 'src/pages/[platform]/build-a-backend/auth/concepts/phone/index.mdx' }, + { + path: 'src/pages/[platform]/build-a-backend/auth/concepts/passwordless/index.mdx' + }, { path: 'src/pages/[platform]/build-a-backend/auth/concepts/user-attributes/index.mdx' }, @@ -137,6 +140,9 @@ export const directory = { { path: 'src/pages/[platform]/build-a-backend/auth/manage-users/manage-passwords/index.mdx' }, + { + path: 'src/pages/[platform]/build-a-backend/auth/manage-users/manage-webauthn-credentials/index.mdx' + }, { path: 'src/pages/[platform]/build-a-backend/auth/manage-users/manage-devices/index.mdx' }, 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 new file mode 100644 index 00000000000..e84aeb8231d --- /dev/null +++ b/src/pages/[platform]/build-a-backend/auth/concepts/passwordless/index.mdx @@ -0,0 +1,147 @@ +import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; + +export const meta = { + title: 'Passwordless', + description: 'Learn how to configure passwordless sign-in flows', + platforms: [ + 'android', + 'angular', + 'javascript', + 'nextjs', + 'react', + 'react-native', + 'swift', + 'vue' + ] +}; + +export function getStaticPaths() { + return getCustomStaticPath(meta.platforms); +} + +export function getStaticProps() { + return { + props: { + meta + } + }; +} + +Amplify supports the use of passwordless authentication flows using the following methods: + +- [SMS-based one-time password (SMS OTP)](#sms-otp) +- [Email-based one-time password (Email OTP)](#email-otp) +- [WebAuthn passkey](#webauthn-passkey) + +Passwordless authentication removes the security risks and user friction associated with traditional passwords. +{/* add more color */} + + + +**Warning:** Passwordless configuration is currently not available in `defineAuth`. We are currently working towards enabling support for passwordless configurations. [Visit the GitHub issue to track the progress](https://github.com/aws-amplify/amplify-backend/issues/2276) + + + +{/* need a section about what a "preferred" factor is */} + +## SMS OTP + +SMS-based authentication uses phone numbers as the identifier and text messages as the verification channel. At a high level end users will perform the following steps to authenticate: + +1. User enters their phone number to sign up/sign in +2. They receive a text message with a time-limited code +3. After the user enters their code they are authenticated + +{/* quick blurb of basic usage */} + + +{/* */} + + + + +{/* */} + + + + + +{/* */} + + + + + +SMS-based one-time password requires your Amazon Cognito user pool to be configured to use Amazon Simple Notification Service (SNS) to send text messages. [Learn how to configure your auth resource with SNS](/[platform]/build-a-backend/auth/moving-to-production/#sms). + +{/* NOTE the linked page will need to be updated with sns instructions */} + + + +[Learn more about using SMS OTP in your application code](/[platform]/build-a-backend/auth/connect-your-frontend/sign-in/#sms-otp). + +## Email OTP + +Email-based authentication uses email addresses for identification and verification. At a high level end users will perform the following steps to authenticate: + +1. User enters their email address to sign up/sign in +2. They receive an email message with a time-limited code +3. After the users enters their code they are authenticated + +{/* quick blurb of basic usage */} + + +{/* */} + + + + +{/* */} + + + + +{/* */} + + + + + +Email-based one-time password requires your Amazon Cognito user pool to be configured to use Amazon Simple Email Service (SES) to send email messages. [Learn how to configure your auth resource with SES](/[platform]/build-a-backend/auth/moving-to-production/#email). + + + +[Learn more about using email OTP in your application code](/[platform]/build-a-backend/auth/connect-your-frontend/sign-in/#email-otp). + +## WebAuthn Passkey + +WebAuthn uses biometrics or security keys for authentication, leveraging device-specific security features. At a high level end users will perform the following steps to authenticate: + +1. User chooses to register a passkey +2. Their device prompts for biometric/security key verification +3. For future logins, they'll authenticate using the same method + +{/* quick blurb of basic usage */} + + +{/* */} + + + + +{/* */} + + + + +{/* */} + + + +[Learn more about using WebAuthn passkeys in your application code](/[platform]/build-a-backend/auth/connect-your-frontend/sign-in/#webauthn-passkeys). + +### Managing credentials + +{/* quick blurb then segue over to "manage WebAuthn credentials" page */} + +[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/connect-your-frontend/multi-step-sign-in/index.mdx b/src/pages/[platform]/build-a-backend/auth/connect-your-frontend/multi-step-sign-in/index.mdx index eee41bf9520..4604f7441da 100644 --- a/src/pages/[platform]/build-a-backend/auth/connect-your-frontend/multi-step-sign-in/index.mdx +++ b/src/pages/[platform]/build-a-backend/auth/connect-your-frontend/multi-step-sign-in/index.mdx @@ -2030,6 +2030,19 @@ func signIn(username: String, password: String) async { // Prompt the user to enter the Email MFA code they received // Then invoke `confirmSignIn` api with the code + + case .continueSignInWithFirstFactorSelection(let allowedFactors): + print("Received next step as continue sign in by selecting first factor") + print("Allowed factors \(allowedFactors)") + + // Prompt the user to select the first factor they want to use + // Then invoke `confirmSignIn` api with the factor + + case .confirmSignInWithPassword: + print("Received next step as confirm sign in with password") + + // Prompt the user to enter the password + // Then invoke `confirmSignIn` api with the password case .continueSignInWithTOTPSetup(let setUpDetails): print("Received next step as continue sign in by setting up TOTP") 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 d018ea92424..78ffb2ea697 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 @@ -259,8 +259,10 @@ The `signIn` API response will include a `nextStep` property, which can be used | `CONFIRM_SIGN_IN_WITH_NEW_PASSWORD_REQUIRED` | The user was created with a temporary password and must set a new one. Complete the process with `confirmSignIn`. | | `CONFIRM_SIGN_IN_WITH_CUSTOM_CHALLENGE` | The sign-in must be confirmed with a custom challenge response. Complete the process with `confirmSignIn`. | | `CONFIRM_SIGN_IN_WITH_TOTP_CODE` | The sign-in must be confirmed with a TOTP code from the user. Complete the process with `confirmSignIn`. | -| `CONFIRM_SIGN_IN_WITH_SMS_CODE` | The sign-in must be confirmed with a SMS code from the user. Complete the process with `confirmSignIn`. | -| `CONFIRM_SIGN_IN_WITH_EMAIL_CODE` | The sign-in must be confirmed with a EMAIL code from the user. Complete the process with `confirmSignIn`. | +| `CONFIRM_SIGN_IN_WITH_SMS_CODE` | The sign-in must be confirmed with an SMS code from the user. Complete the process with `confirmSignIn`. | +| `CONFIRM_SIGN_IN_WITH_EMAIL_CODE` | The sign-in must be confirmed with an EMAIL code from the user. Complete the process with `confirmSignIn`. | +| `CONFIRM_SIGN_IN_WITH_PASSWORD` | The sign-in must be confirmed with the password from the user. Complete the process with `confirmSignIn`. | +| `CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION` | The user must select their mode of first factor authentication. Complete the process by passing the desired mode to the `challengeResponse` field of `confirmSignIn`. | | `CONTINUE_SIGN_IN_WITH_MFA_SELECTION` | The user must select their mode of MFA verification before signing in. Complete the process with `confirmSignIn`. | | `CONTINUE_SIGN_IN_WITH_MFA_SETUP_SELECTION` | The user must select their mode of MFA verification to setup. Complete the process by passing either `"EMAIL"` or `"TOTP"` to `confirmSignIn`. | | `CONTINUE_SIGN_IN_WITH_TOTP_SETUP` | The TOTP setup process must be continued. Complete the process with `confirmSignIn`. | @@ -295,6 +297,8 @@ The `signIn` API response will include a `nextStep` property, which can be used | `confirmSignInWithTOTPCode` | The sign-in must be confirmed with a TOTP code from the user. Complete the process with `confirmSignIn`. | | `confirmSignInWithSMSMFACode` | The sign-in must be confirmed with a SMS code from the user. Complete the process with `confirmSignIn`. | | `confirmSignInWithOTP` | The sign-in must be confirmed with a code from the user (sent via SMS or Email). Complete the process with `confirmSignIn`. | +| `confirmSignInWithPassword` | The user must set a new password. Complete the process with `confirmSignIn`. | +| `continueSignInWithFirstFactorSelection` | The user must select their preferred mode of First Factor authentication. Complete the process with `confirmSignIn`. | | `continueSignInWithMFASelection` | The user must select their mode of MFA verification before signing in. Complete the process with `confirmSignIn`. | | `continueSignInWithMFASetupSelection` | The user must select their mode of MFA verification to setup. Complete the process by passing either `MFAType.email.challengeResponse` or `MFAType.totp.challengeResponse ` to `confirmSignIn`. | | `continueSignInWithTOTPSetup` | The TOTP setup process must be continued. Complete the process with `confirmSignIn`. | @@ -589,6 +593,8 @@ Following sign in, you will receive a `nextStep` in the sign-in result of one of | `CONFIRM_SIGN_IN_WITH_TOTP_CODE` | The sign-in must be confirmed with a TOTP code from the user. Complete the process with `confirmSignIn`. | | `CONFIRM_SIGN_IN_WITH_SMS_CODE` | The sign-in must be confirmed with a SMS code from the user. Complete the process with `confirmSignIn`. | | `CONFIRM_SIGN_IN_WITH_EMAIL_CODE` | The sign-in must be confirmed with a EMAIL code from the user. Complete the process with `confirmSignIn`. | +| `CONFIRM_SIGN_IN_WITH_PASSWORD` | The sign-in must be confirmed with the password from the user. Complete the process with `confirmSignIn`. | +| `CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION` | The user must select their mode of first factor authentication. Complete the process by passing the desired mode to the `challengeResponse` field of `confirmSignIn`. | | `CONTINUE_SIGN_IN_WITH_MFA_SELECTION` | The user must select their mode of MFA verification before signing in. Complete the process with `confirmSignIn`. | | `CONTINUE_SIGN_IN_WITH_MFA_SETUP_SELECTION` | The user must select their mode of MFA verification to setup. Complete the process by passing either `"EMAIL"` or `"TOTP"` to `confirmSignIn`. | | `CONTINUE_SIGN_IN_WITH_TOTP_SETUP` | The TOTP setup process must be continued. Complete the process with `confirmSignIn`. | @@ -615,6 +621,8 @@ Following sign in, you will receive a `nextStep` in the sign-in result of one of | `confirmSignInWithTOTPCode` | The sign-in must be confirmed with a TOTP code from the user. Complete the process with `confirmSignIn`. | | `confirmSignInWithSMSMFACode` | The sign-in must be confirmed with a SMS code from the user. Complete the process with `confirmSignIn`. | | `confirmSignInWithOTP` | The sign-in must be confirmed with a code from the user (sent via SMS or Email). Complete the process with `confirmSignIn`. | +| `confirmSignInWithPassword` | The user must set a new password. Complete the process with `confirmSignIn`. | +| `continueSignInWithFirstFactorSelection` | The user must select their preferred mode of First Factor authentication. Complete the process with `confirmSignIn`. | | `continueSignInWithMFASelection` | The user must select their mode of MFA verification before signing in. Complete the process with `confirmSignIn`. | | `continueSignInWithMFASetupSelection` | The user must select their mode of MFA verification to setup. Complete the process by passing either `MFAType.email.challengeResponse` or `MFAType.totp.challengeResponse ` to `confirmSignIn`. | | `continueSignInWithTOTPSetup` | The TOTP setup process must be continued. Complete the process with `confirmSignIn`. | @@ -1099,4 +1107,276 @@ func socialSignInWithWebUI() -> AnyCancellable { -{/* sign-in with web UI (this is a very limited alternative to the Authenticator) */} + + + +## 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/) + +### SMS OTP + +{/* blurb with supplemental information about handling sign-in, events, etc. */} + + + +To request an OTP code via SMS for authentication, the challenge is passed as the challenge response to the confirm sign in API. + +Amplify will respond appropriately to Cognito and return the challenge as sign in next step: `CONFIRM_SIGN_IN_WITH_SMS_CODE`: + +```ts +const { nextStep } = await confirmSignIn({ + challengeResponse: "SMS_OTP" +}); + +// nextStep.signInStep === 'CONFIRM_SIGN_IN_WITH_SMS_CODE' +handleNextSignInStep(nextStep); +``` + + + + +{/* */} + + + + + + + +```swift +// sign in with `smsOTP` as preferred factor +func signIn(username: String) async { + do { + let pluginOptions = AWSAuthSignInOptions( + authFlowType: .userAuth(preferredFirstFactor: .smsOTP)) + 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)") + } +} + +// confirm sign in with the code received +func confirmSignIn() async { + do { + let signInResult = try await Amplify.Auth.confirmSignIn(challengeResponse: "") + print("Confirm sign in succeeded. Next step: \(signInResult.nextStep)") + } catch let error as AuthError { + print("Confirm sign in failed \(error)") + } catch { + print("Unexpected error: \(error)") + } +} + +``` + + + + +```swift +// sign in with `smsOTP` as preferred factor +func signIn(username: String) -> AnyCancellable { + Amplify.Publisher.create { + let pluginOptions = AWSAuthSignInOptions( + authFlowType: .userAuth(preferredFirstFactor: .smsOTP)) + 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)") + } +} + +// confirm sign in with the code received +func confirmSignIn() -> AnyCancellable { + Amplify.Publisher.create { + try await Amplify.Auth.confirmSignIn(challengeResponse: "") + }.sink { + if case let .failure(authError) = $0 { + print("Confirm sign in failed \(authError)") + } + } + receiveValue: { signInResult in + print("Confirm sign in succeeded. Next step: \(signInResult.nextStep)") + } +} +``` + + + + + + +### Email OTP + +{/* blurb with supplemental information about handling sign-in, events, etc. */} + + + +To request an OTP code via email for authentication, the challenge is passed as the challenge response to the confirm sign in API. + +Amplify will respond appropriately to Cognito and return the challenge as sign in next step: `CONFIRM_SIGN_IN_WITH_EMAIL_CODE`: + +```ts +const { nextStep } = await confirmSignIn({ + challengeResponse: "EMAIL_OTP" +}); + +// nextStep.signInStep === 'CONFIRM_SIGN_IN_WITH_EMAIL_CODE' +handleNextSignInStep(nextStep); +``` + + + + +{/* */} + + + + + + + +```swift +// sign in with `emailOTP` as preferred factor +func signIn(username: String) async { + do { + let pluginOptions = AWSAuthSignInOptions( + authFlowType: .userAuth(preferredFirstFactor: .emailOTP)) + 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)") + } +} + +// confirm sign in with the code received +func confirmSignIn() async { + do { + let signInResult = try await Amplify.Auth.confirmSignIn(challengeResponse: "") + print("Confirm sign in succeeded. Next step: \(signInResult.nextStep)") + } catch let error as AuthError { + print("Confirm sign in failed \(error)") + } catch { + print("Unexpected error: \(error)") + } +} + +``` + + + + +```swift +// sign in with `emailOTP` as preferred factor +func signIn(username: String) -> AnyCancellable { + Amplify.Publisher.create { + let pluginOptions = AWSAuthSignInOptions( + authFlowType: .userAuth(preferredFirstFactor: .emailOTP)) + 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)") + } +} + +// confirm sign in with the code received +func confirmSignIn() -> AnyCancellable { + Amplify.Publisher.create { + try await Amplify.Auth.confirmSignIn(challengeResponse: "") + }.sink { + if case let .failure(authError) = $0 { + print("Confirm sign in failed \(authError)") + } + } + receiveValue: { signInResult in + print("Confirm sign in succeeded. Next step: \(signInResult.nextStep)") + } +} +``` + + + + + + +### WebAuthn Passkeys + +{/* blurb with supplemental information about handling sign-in, events, etc. */} + + + +The WebAuthn credential flow is initiated by passing the challenge name to the confirm sign in api. + +```ts +const { nextStep } = await confirmSignIn({ + challengeResponse: "WEB_AUTHN", +}); + +// nextStep.signInStep === 'DONE' +handleNextSignInStep(nextStep); +``` + + + + +{/* */} + + + + +{/* */} + + + + +{/* */} + + + + + +### Password or SRP + +Traditional password based authentication is available from this flow as well. To initiate this flow from select challenge, either `PASSWORD` or `PASSWORD_SRP` is passed as the challenge response. + +```ts +const { nextStep } = await confirmSignIn({ + challengeResponse: "PASSWORD_SRP", // or "PASSWORD" +}); + +// in both cases +// nextStep.signInStep === 'CONFIRM_SIGN_IN_WITH_PASSWORD' +handleNextSignInStep(nextStep); + +const { nextStep: nextNextStep } = await confirmSignIn({ + challengeResponse: "Test123#", +}); + +// nextNextStep.signInStep === 'DONE' +handleNextSignInStep(nextNextStep); +``` + + + + diff --git a/src/pages/[platform]/build-a-backend/auth/connect-your-frontend/sign-up/index.mdx b/src/pages/[platform]/build-a-backend/auth/connect-your-frontend/sign-up/index.mdx index 692d8f82939..436ce634a0e 100644 --- a/src/pages/[platform]/build-a-backend/auth/connect-your-frontend/sign-up/index.mdx +++ b/src/pages/[platform]/build-a-backend/auth/connect-your-frontend/sign-up/index.mdx @@ -527,3 +527,411 @@ export default function App() { + + + +## Sign up with passwordless methods + +Your application's users can also sign up using passwordless methods. To learn more, visit the [concepts page for passwordless](/[platform]/build-a-backend/auth/concepts/passwordless/) + +### SMS OTP + +{/* blurb with supplemental information about handling sign-up, events, etc. */} + + + +```typescript +// Sign up using a phone number +const { nextStep: signUpNextStep } = await signUp({ + username: 'james', + options: { + userAttributes: { + phone_number: '+15555551234', + }, + }, +}); + +if (signUpNextStep.signUpStep === 'DONE') { + console.log(`SignUp Complete`); +} + +if (signUpNextStep.signUpStep === 'CONFIRM_SIGN_UP') { + console.log( + `Code Delivery Medium: ${signUpNextStep.codeDeliveryDetails.deliveryMedium}`, + ); + console.log( + `Code Delivery Destination: ${signUpNextStep.codeDeliveryDetails.destination}`, + ); +} + +// Confirm sign up with the OTP received +const { nextStep: confirmSignUpNextStep } = await confirmSignUp({ + username: 'james', + confirmationCode: '123456', +}); + +if (confirmSignUpNextStep.signUpStep === 'DONE') { + console.log(`SignUp Complete`); +} +``` + + + + +{/* */} + + + + + + + + +```swift +// Sign up using an phone number +func signUp(username: String, phonenumber: String) async { + let userAttributes = [ + AuthUserAttribute(.phoneNumber, value: phonenumber) + ] + let options = AuthSignUpRequest.Options(userAttributes: userAttributes) + do { + let signUpResult = try await Amplify.Auth.signUp( + username: username, + options: options + ) + if case let .confirmUser(deliveryDetails, _, userId) = signUpResult.nextStep { + print("Delivery details \(String(describing: deliveryDetails)) for userId: \(String(describing: userId)))") + } else { + print("SignUp Complete") + } + } catch let error as AuthError { + print("An error occurred while registering a user \(error)") + } catch { + print("Unexpected error: \(error)") + } +} + +// Confirm sign up with the OTP received +func confirmSignUp(for username: String, with confirmationCode: String) async { + do { + let confirmSignUpResult = try await Amplify.Auth.confirmSignUp( + for: username, + confirmationCode: confirmationCode + ) + print("Confirm sign up result completed: \(confirmSignUpResult.isSignUpComplete)") + } catch let error as AuthError { + print("An error occurred while confirming sign up \(error)") + } catch { + print("Unexpected error: \(error)") + } +} +``` + + + + + +```swift +// Sign up using a phone number +func signUp(username: String, phonenumber: String) -> AnyCancellable { + let userAttributes = [ + AuthUserAttribute(.phoneNumber, value: phonenumber) + ] + let options = AuthSignUpRequest.Options(userAttributes: userAttributes) + let sink = Amplify.Publisher.create { + try await Amplify.Auth.signUp( + username: username, + options: options + ) + }.sink { + if case let .failure(authError) = $0 { + print("An error occurred while registering a user \(authError)") + } + } + receiveValue: { signUpResult in + if case let .confirmUser(deliveryDetails, _, userId) = signUpResult.nextStep { + print("Delivery details \(String(describing: deliveryDetails)) for userId: \(String(describing: userId)))") + } else { + print("SignUp Complete") + } + } + return sink +} + +// Confirm sign up with the OTP received +func confirmSignUp(for username: String, with confirmationCode: String) -> AnyCancellable { + Amplify.Publisher.create { + try await Amplify.Auth.confirmSignUp( + for: username, + confirmationCode: confirmationCode + ) + }.sink { + if case let .failure(authError) = $0 { + print("An error occurred while confirming sign up \(authError)") + } + } + receiveValue: { _ in + print("Confirm signUp succeeded") + } +} +``` + + + + + + + +### Email OTP + +{/* blurb with supplemental information about handling sign-up, events, etc. */} + + + +```typescript +// Sign up using an email address +const { nextStep: signUpNextStep } = await signUp({ + username: 'james', + options: { + userAttributes: { + email: 'james@example.com', + }, + }, +}); + +if (signUpNextStep.signUpStep === 'DONE') { + console.log(`SignUp Complete`); +} + +if (signUpNextStep.signUpStep === 'CONFIRM_SIGN_UP') { + console.log( + `Code Delivery Medium: ${signUpNextStep.codeDeliveryDetails.deliveryMedium}`, + ); + console.log( + `Code Delivery Destination: ${signUpNextStep.codeDeliveryDetails.destination}`, + ); +} + +// Confirm sign up with the OTP received +const { nextStep: confirmSignUpNextStep } = await confirmSignUp({ + username: 'james', + confirmationCode: '123456', +}); + +if (confirmSignUpNextStep.signUpStep === 'DONE') { + console.log(`SignUp Complete`); +} +``` + + + + +{/* */} + + + + + + + + +```swift +// Sign up using an email +func signUp(username: String, email: String) async { + let userAttributes = [ + AuthUserAttribute(.email, value: email) + ] + let options = AuthSignUpRequest.Options(userAttributes: userAttributes) + do { + let signUpResult = try await Amplify.Auth.signUp( + username: username, + options: options + ) + if case let .confirmUser(deliveryDetails, _, userId) = signUpResult.nextStep { + print("Delivery details \(String(describing: deliveryDetails)) for userId: \(String(describing: userId)))") + } else { + print("SignUp Complete") + } + } catch let error as AuthError { + print("An error occurred while registering a user \(error)") + } catch { + print("Unexpected error: \(error)") + } +} + +// Confirm sign up with the OTP received +func confirmSignUp(for username: String, with confirmationCode: String) async { + do { + let confirmSignUpResult = try await Amplify.Auth.confirmSignUp( + for: username, + confirmationCode: confirmationCode + ) + print("Confirm sign up result completed: \(confirmSignUpResult.isSignUpComplete)") + } catch let error as AuthError { + print("An error occurred while confirming sign up \(error)") + } catch { + print("Unexpected error: \(error)") + } +} +``` + + + + + +```swift +// Sign up using an email +func signUp(username: String, email: String) -> AnyCancellable { + let userAttributes = [ + AuthUserAttribute(.email, value: email) + ] + let options = AuthSignUpRequest.Options(userAttributes: userAttributes) + let sink = Amplify.Publisher.create { + try await Amplify.Auth.signUp( + username: username, + options: options + ) + }.sink { + if case let .failure(authError) = $0 { + print("An error occurred while registering a user \(authError)") + } + } + receiveValue: { signUpResult in + if case let .confirmUser(deliveryDetails, _, userId) = signUpResult.nextStep { + print("Delivery details \(String(describing: deliveryDetails)) for userId: \(String(describing: userId)))") + } else { + print("SignUp Complete") + } + } + return sink +} + +// Confirm sign up with the OTP received +func confirmSignUp(for username: String, with confirmationCode: String) -> AnyCancellable { + Amplify.Publisher.create { + try await Amplify.Auth.confirmSignUp( + for: username, + confirmationCode: confirmationCode + ) + }.sink { + if case let .failure(authError) = $0 { + print("An error occurred while confirming sign up \(authError)") + } + } + receiveValue: { _ in + print("Confirm signUp succeeded") + } +} +``` + + + + + + + +### Auto Sign In + +{/* blurb with supplemental information about auto sign in */} + + + +```typescript +// Confirm sign up with the OTP received and auto sign in +const { nextStep: confirmSignUpNextStep } = await confirmSignUp({ + username: 'james', + confirmationCode: '123456', +}); + +if (confirmSignUpNextStep.signUpStep === 'COMPLETE_AUTO_SIGN_IN') { + const { nextStep } = await autoSignIn(); + + if (nextStep.signInStep === 'DONE') { + console.log('Successfully signed in.'); + } +} +``` + + + +{/* */} + + + + + + + + +```swift +// Confirm sign up with the OTP received and auto sign in +func confirmSignUp(for username: String, with confirmationCode: String) async { + do { + let confirmSignUpResult = try await Amplify.Auth.confirmSignUp( + for: username, + confirmationCode: confirmationCode + ) + if case .completeAutoSignIn(let session) = confirmSignUpResult.nextStep { + let autoSignInResult = try await Amplify.Auth.autoSignIn() + print("Auto sign in result: \(autoSignInResult.isSignedIn)") + } else { + print("Confirm sign up result completed: \(confirmSignUpResult.isSignUpComplete)") + } + } catch let error as AuthError { + print("An error occurred while confirming sign up \(error)") + } catch { + print("Unexpected error: \(error)") + } +} +``` + + + + + +```swift +// Confirm sign up with the OTP received and auto sign in +func confirmSignUp(for username: String, with confirmationCode: String) -> AnyCancellable { + Amplify.Publisher.create { + try await Amplify.Auth.confirmSignUp( + for: username, + confirmationCode: confirmationCode + ) + }.sink { + if case let .failure(authError) = $0 { + print("An error occurred while confirming sign up \(authError)") + } + } + receiveValue: { confirmSignUpResult in + if case let .completeAutoSignIn(session) = confirmSignUpResult.nextStep { + print("Confirm Sign Up succeeded. Next step is auto sign in") + // call `autoSignIn()` API to complete sign in + } else { + print("Confirm sign up result completed: \(confirmSignUpResult.isSignUpComplete)") + } + } +} + +func autoSignIn() -> AnyCancellable { + Amplify.Publisher.create { + try await Amplify.Auth.autoSignIn() + }.sink { + if case let .failure(authError) = $0 { + print("Auto Sign in failed \(authError)") + } + } + receiveValue: { autoSignInResult in + if autoSignInResult.isSignedIn { + print("Auto Sign in succeeded") + } + } +} +``` + + + + + + + + diff --git a/src/pages/[platform]/build-a-backend/auth/connect-your-frontend/switching-authentication-flows/index.mdx b/src/pages/[platform]/build-a-backend/auth/connect-your-frontend/switching-authentication-flows/index.mdx index dbddf87f4bb..5c82f2da750 100644 --- a/src/pages/[platform]/build-a-backend/auth/connect-your-frontend/switching-authentication-flows/index.mdx +++ b/src/pages/[platform]/build-a-backend/auth/connect-your-frontend/switching-authentication-flows/index.mdx @@ -40,6 +40,8 @@ For client side authentication there are four different flows that can be config 4. `customWithoutSRP`: The `customWithoutSRP` flow is used to start authentication flow **WITHOUT** SRP and then use a series of challenge and response cycles that can be customized to meet different requirements. +5. `userAuth`: The `userAuth` flow is a choice-based authentication flow that allows the user to choose from the list of available authentication methods. This flow is useful when you want to provide the user with the option to choose the authentication method. The choices that may be available to the user are `emailOTP`, `smsOTP`, `webAuthn`, `password` or `passwordSRP`. + `Auth` can be configured to use the different flows at runtime by calling `signIn` with `AuthSignInOptions`'s `authFlowType` as `AuthFlowType.userPassword`, `AuthFlowType.customAuthWithoutSrp` or `AuthFlowType.customAuthWithSrp`. If you do not specify the `AuthFlowType` in `AuthSignInOptions`, the default flow (`AuthFlowType.userSRP`) will be used. @@ -50,6 +52,31 @@ Runtime configuration will take precedence and will override any auth flow type > For more information about authentication flows, please visit [Amazon Cognito developer documentation](https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-authentication-flow.html#amazon-cognito-user-pools-custom-authentication-flow) +## USER_AUTH (Choice-based authentication) flow + +A use case for the `USER_AUTH` authentication flow is to provide the user with the option to choose the authentication method. The choices that may be available to the user are `emailOTP`, `smsOTP`, `webAuthn`, `password` or `passwordSRP`. + +```swift +let pluginOptions = AWSAuthSignInOptions( + authFlowType: .userAuth) +let signInResult = try await Amplify.Auth.signIn( + username: username, + password: password, + options: .init(pluginOptions: pluginOptions)) +guard case .continueSignInWithFirstFactorSelection(let availableFactors) = signInResult.nextStep else { + return +} +print("Available factors: \(availableFactors)") +``` + +The selection of the authentication method is done by the user. The user can choose from the available factors and proceed with the selected factor. You should call the `confirmSignIn` API with the selected factor to continue the sign-in process. Following is an example if you want to proceed with the `emailOTP` factor selection: + +```swift +// Select emailOTP as the factor +var confirmSignInResult = try await Amplify.Auth.confirmSignIn( + challengeResponse: AuthFactorType.emailOTP.challengeResponse) +``` + ## USER_PASSWORD_AUTH flow A use case for the `USER_PASSWORD_AUTH` authentication flow is migrating users into Amazon Cognito @@ -92,6 +119,7 @@ const backend = defineBackend({ backend.auth.resources.cfnResources.cfnUserPoolClient.explicitAuthFlows = [ "ALLOW_USER_PASSWORD_AUTH", "ALLOW_USER_SRP_AUTH", + "ALLOW_USER_AUTH", "ALLOW_REFRESH_TOKEN_AUTH" ]; // highlight-end @@ -117,7 +145,7 @@ Follow the instructions in [Custom Auth Sign In](/gen1/[platform]/build-a-backen -For client side authentication there are three different flows: +For client side authentication there are four different flows: 1. `USER_SRP_AUTH`: The `USER_SRP_AUTH` flow uses the [SRP protocol (Secure Remote Password)](https://en.wikipedia.org/wiki/Secure_Remote_Password_protocol) where the password never leaves the client and is unknown to the server. This is the recommended flow and is used by default. @@ -125,20 +153,66 @@ For client side authentication there are three different flows: 3. `CUSTOM_WITH_SRP` & `CUSTOM_WITHOUT_SRP`: Allows for a series of challenge and response cycles that can be customized to meet different requirements. +4. `USER_AUTH`: The `USER_AUTH` flow is a choice-based authentication flow that allows the user to choose from the list of available authentication methods. This flow is useful when you want to provide the user with the option to choose the authentication method. The choices that may be available to the user are `EMAIL_OTP`, `SMS_OTP`, `WEB_AUTHN`, `PASSWORD` or `PASSWORD_SRP`. + The Auth flow can be customized when calling `signIn`, for example: ```ts title="src/main.ts" await signIn({ - username: "hello@mycompany.com", + username: "hello@mycompany.com", password: "hunter2", options: { - authFlowType: 'USER_PASSWORD_AUTH' + authFlowType: 'USER_AUTH' } }) ``` > For more information about authentication flows, please visit [AWS Cognito developer documentation](https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-authentication-flow.html#amazon-cognito-user-pools-custom-authentication-flow) +## USER_AUTH flow + +The `USER_AUTH` sign in flow will support the following methods of first factor authentication: `WEB_AUTHN`, `EMAIL_OTP`, `SMS_OTP`, `PASSWORD`, and `PASSWORD_SRP`. + +```ts +type AuthFactorType = + | "WEB_AUTHN" + | "EMAIL_OTP" + | "SMS_OTP" + | "PASSWORD" + | "PASSWORD_SRP"; +``` + +If the desired first factor is known before the sign in flow is initiated it can be passed to the initial sign in call. + +```ts +// PASSWORD_SRP / PASSWORD +// sign in with preferred challenge as password +// note password must be provided in same step +const { nextStep } = await signIn({ + username: "hello@mycompany.com", + password: "hunter2", + options: { + authFlowType: "USER_AUTH", + preferredChallenge: "PASSWORD_SRP" // or "PASSWORD" + }, +}); + +// WEB_AUTHN / EMAIL_OTP / SMS_OTP +// sign in with preferred passwordless challenge +// no user input required at this step +const { nextStep } = await signIn({ + username: "passwordless@mycompany.com", + options: { + authFlowType: "USER_AUTH", + preferredChallenge: "WEB_AUTHN" // or "EMAIL_OTP" or "SMS_OTP" + }, +}); +``` + +If the desired first factor is not known, the flow will continue to select an available first factor. + +> For more information about determining a first factor, and signing in with passwordless authorization factors, please visit the [concepts page for passwordless](/[platform]/build-a-backend/auth/concepts/passwordless/) + ## USER_PASSWORD_AUTH flow A use case for the `USER_PASSWORD_AUTH` authentication flow is migrating users into Amazon Cognito 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 new file mode 100644 index 00000000000..1f31f20593b --- /dev/null +++ b/src/pages/[platform]/build-a-backend/auth/manage-users/manage-webauthn-credentials/index.mdx @@ -0,0 +1,280 @@ +import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; + +export const meta = { + title: 'Manage WebAuthn credentials', + description: 'Learn how to manage WebAuthn credentials', + platforms: [ + 'android', + 'angular', + 'javascript', + 'nextjs', + 'react', + 'react-native', + 'swift', + 'vue' + ] +}; + +export function getStaticPaths() { + return getCustomStaticPath(meta.platforms); +} + +export function getStaticProps() { + return { + props: { + meta, + } + }; +} + + + + +WebAuthn registration and authentication are not currently supported on React Native, other passwordless features are fully supported. + + + + +Amplify Auth enables your users to associate, keep track of, and delete passkeys. + +## Associate WebAuthN credentials + +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. + +You can associate a passkey using the following API: + + + +```ts +import { associateWebAuthnCredential} from 'aws-amplify/auth'; + +await associateWebAuthnCredential(); + +``` + + + + +{/* */} + + + + + + + +```swift +func associateWebAuthNCredentials() async { + do { + try await Amplify.Auth.associateWebAuthnCredential() + print("WebAuthn credential was associated") + } catch { + print("Associate WebAuthn Credential failed: \(error)") + } +} +``` + + + + +```swift +func associateWebAuthNCredentials() -> AnyCancellable { + Amplify.Publisher.create { + try await Amplify.Auth.associateWebAuthnCredential() + }.sink { + print("Associate WebAuthn Credential failed: \($0)") + } + receiveValue: { _ in + print("WebAuthn credential was associated") + } +} +``` + + + + + + +You will be prompted to register a passkey using your local authenticator. Amplify will then associate that passkey with Cognito. + +## List WebAuthN credentials + +You can list registered passkeys using the following API: + + + +```ts +import { listWebAuthnCredentials } from 'aws-amplify/auth'; + +const result = await listWebAuthnCredentials(); + +for (const credential of result.credentials) { + console.log(`Credential ID: ${credential.credentialId}`); + console.log(`Friendly Name: ${credential.friendlyCredentialName}`); + console.log(`Relying Party ID: ${credential.relyingPartyId}`); + console.log(`Created At: ${credential.createdAt}`); +} + +``` + + + +{/* */} + + + + + + + +```swift +func listWebAuthNCredentials() async { + do { + let result = try await Amplify.Auth.listWebAuthnCredentials( + options: .init(pageSize: 5)) + + for credential in result.credentials { + print("Credential ID: \(credential.credentialId)") + print("Created At: \(credential.createdAt)") + print("Relying Party Id: \(credential.relyingPartyId)") + if let friendlyName = credential.friendlyName { + print("Friendly name: \(friendlyName)") + } + } + + // Fetch the next page + if let nextToken = result.nextToken { + let nextResult = try await Amplify.Auth.listWebAuthnCredentials( + options: .init( + pageSize: 5, + nextToken: nextToken)) + } + } catch { + print("Associate WebAuthn Credential failed: \(error)") + } +} +``` + + + + +```swift +func listWebAuthNCredentials() -> AnyCancellable { + Amplify.Publisher.create { + try await Amplify.Auth.listWebAuthnCredentials( + options: .init(pageSize: 5)) + }.sink { + print("List WebAuthn Credential failed: \($0)") + } + receiveValue: { result in + for credential in result.credentials { + print("Credential ID: \(credential.credentialId)") + print("Created At: \(credential.createdAt)") + print("Relying Party Id: \(credential.relyingPartyId)") + if let friendlyName = credential.friendlyName { + print("Friendly name: \(friendlyName)") + } + } + + if let nextToken = result.nextToken { + // Fetch the next page + } + } +} +``` + + + + + + +## Delete WebAuthN credentials + +You can delete a passkey with the following API: + + + +```ts +import { deleteWebAuthnCredential } from 'aws-amplify/auth'; + +const id = "credential-id-to-delete"; + +await deleteWebAuthnCredential({ + credentialId: id +}); +``` + + + +{/* */} + + + + + + + +```swift +func deleteWebAuthNCredentials(credentialId: String) async { + do { + try await Amplify.Auth.deleteWebAuthnCredential(credentialId: credentialId) + print("WebAuthn credential was deleted") + } catch { + print("Delete WebAuthn Credential failed: \(error)") + } +} +``` + + + + +```swift +func deleteWebAuthNCredentials(credentialId: String) -> AnyCancellable { + Amplify.Publisher.create { + try await Amplify.Auth.deleteWebAuthnCredential(credentialId: credentialId) + }.sink { + print("Delete WebAuthn Credential failed: \($0)") + } + receiveValue: { _ in + print("WebAuthn credential was deleted") + } +} +``` + + + + + + + +## Practical example + +Here is a code example that uses the list and delete APIs together. In this example, the user has 3 passkeys registered. They want to list all passkeys while using a `pageSize` of 2 as well as delete the first passkey in the list. + +```ts +import { + listWebAuthnCredentials, + deleteWebAuthnCredential +} from 'aws-amplify/auth'; + +let passkeys = []; + +const result = await listWebAuthnCredentials({ pageSize: 2 }); + +passkeys.push(...result.credentials); + +const nextPage = await listWebAuthnCredentials({ + pageSize: 2, + nextToken: result.nextToken, +}); + +passkeys.push(...nextPage.credentials); + +const id = passkeys[0].credentialId; + +await deleteWebAuthnCredential({ + credentialId: id +}); +``` + + 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 3d5c3b8b143..94b775153fd 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 @@ -58,6 +58,7 @@ cfnUserPool.policies = { ``` ## Override Cognito UserPool multi-factor authentication options + While Email MFA is not yet supported with `defineAuth`, this feature can be enabled by modifying the underlying CDK construct. Start by ensuring your `defineAuth` resource configuration includes a compatible account recovery option and a custom SES sender. @@ -89,6 +90,7 @@ export const auth = defineAuth({ }, }) ``` + Next, extend the underlying CDK construct by activating [Amazon Cognito's Advanced Security Features (ASF)](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pool-settings-advanced-security.html) and add `EMAIL_OTP` to the enabled MFA options. ```ts title="amplify/backend.ts" @@ -114,3 +116,33 @@ cfnUserPool.enabledMfas = [...(cfnUserPool.enabledMfas || []), "EMAIL_OTP"] {/* token validity */} {/* BYO custom idp construct */} {/* extend auth/unauth roles */} + +### 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/). + +```ts title="amplify/backend.ts" +import { defineBackend } from "@aws-amplify/backend" +import { auth } from "./auth/resource" + +const backend = defineBackend({ + auth, +}) + +const { cfnResources } = backend.auth.resources; +const { cfnUserPool, cfnUserPoolClient } = cfnResources; + +cfnUserPool.addPropertyOverride( + 'Policies.SignInPolicy.AllowedFirstAuthFactors', + ['PASSWORD', 'WEB_AUTHN', 'EMAIL_OTP', 'SMS_OTP'] +); + +cfnUserPoolClient.explicitAuthFlows = [ + 'ALLOW_REFRESH_TOKEN_AUTH', + 'ALLOW_USER_AUTH' +]; + +/* Needed for WebAuthn */ +cfnUserPool.addPropertyOverride('WebAuthnRelyingPartyID', ''); +cfnUserPool.addPropertyOverride('WebAuthnUserVerification', 'preferred'); +```