From c863841d531350d75e0f371136ae537fc2c11aab Mon Sep 17 00:00:00 2001 From: josef Date: Tue, 26 Nov 2024 17:55:31 -0600 Subject: [PATCH 01/25] Add passwordless (#8127) * initial ia * concept boilerplate * add code snippet placeholders, cross links, warning for backend support * chore(auth): add swift passwordless sign up/auto sign in changes (#8128) * Apply suggestions from code review * rm comments, stale sections * chore: add new steps and new flow type * chore(auth): add swift passwordless sign in/web authn changes (#8131) * chore(auth): add swift passwordless sign in/web authn changes * removed unused section * remove flutter blocks * fix spelling * chore(auth): update swift passwordless autosignin Combine snippets (#8132) * chore(auth): add js passwordless changes (#8129) * chore(auth): add js passwordless changes * chore(auth): add filters and a react-native callout; code spacing * chore(auth): add sign in updates * Update src/pages/[platform]/build-a-backend/auth/manage-users/manage-webauthn-credentials/index.mdx Co-authored-by: James Jarvis * chore(auth): add associate api to the manage webauthn page * Update src/pages/[platform]/build-a-backend/auth/connect-your-frontend/sign-in/index.mdx Co-authored-by: josef * chore(auth): use string literals * chore(auth): add signin next step types * add js sign up examples * chore(auth): improve wording * chore(auth): add first factor selection to confirm sign-in next steps * Update src/pages/[platform]/build-a-backend/auth/connect-your-frontend/sign-in/index.mdx Co-authored-by: James Jarvis * Update src/pages/[platform]/build-a-backend/auth/connect-your-frontend/sign-in/index.mdx Co-authored-by: James Jarvis * chore(auth): review feedback - formatting and wording * chore(auth): move user auth flow info into switching flows page --------- Co-authored-by: James Jarvis Co-authored-by: josef * resolve structural issues * fix extra closing tag * filter passwordless blocks * Update src/pages/[platform]/build-a-backend/auth/concepts/passwordless/index.mdx * revert auth flow order change * update user auth flow description --------- Co-authored-by: Abhash Kumar Singh Co-authored-by: Harsh <6162866+harsh62@users.noreply.github.com> Co-authored-by: Parker Scanlon <69879391+scanlonp@users.noreply.github.com> Co-authored-by: James Jarvis --- cspell.json | 1 + src/directory/directory.mjs | 6 + .../auth/concepts/passwordless/index.mdx | 147 +++++++ .../multi-step-sign-in/index.mdx | 13 + .../connect-your-frontend/sign-in/index.mdx | 286 +++++++++++- .../connect-your-frontend/sign-up/index.mdx | 408 ++++++++++++++++++ .../switching-authentication-flows/index.mdx | 80 +++- .../manage-webauthn-credentials/index.mdx | 280 ++++++++++++ .../auth/modify-resources-with-cdk/index.mdx | 32 ++ 9 files changed, 1247 insertions(+), 6 deletions(-) create mode 100644 src/pages/[platform]/build-a-backend/auth/concepts/passwordless/index.mdx create mode 100644 src/pages/[platform]/build-a-backend/auth/manage-users/manage-webauthn-credentials/index.mdx 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'); +``` From 943d6818160c11d3e9368d5a288a77eaad873123 Mon Sep 17 00:00:00 2001 From: Tiffany Yeung Date: Wed, 27 Nov 2024 08:09:27 -0800 Subject: [PATCH 02/25] fix: update Next.js pages link to latest ver (#8126) --- Readme.md | 2 +- src/fragments/lib-v1/ssr/js/next-js-callout.mdx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Readme.md b/Readme.md index 4610e6201fc..60975e0a178 100644 --- a/Readme.md +++ b/Readme.md @@ -37,7 +37,7 @@ We welcome contributions to the documentation site! Here's how to do it: ## Authoring pages -Our docs are generated using [Next.js](https://nextjs.org/). Refer to their docs on [how to create pages](https://nextjs.org/docs/basic-features/pages) as a primer. +Our docs are generated using [Next.js](https://nextjs.org/). Refer to their docs on [how to create pages](https://nextjs.org/docs/pages/building-your-application/routing/pages-and-layouts) as a primer. The source for each page is in **src**. This folder is the only directory you need to touch to edit or create pages. diff --git a/src/fragments/lib-v1/ssr/js/next-js-callout.mdx b/src/fragments/lib-v1/ssr/js/next-js-callout.mdx index 85fe4b6bafd..eed5f6c322a 100644 --- a/src/fragments/lib-v1/ssr/js/next-js-callout.mdx +++ b/src/fragments/lib-v1/ssr/js/next-js-callout.mdx @@ -1,5 +1,5 @@ -SSR functionality in Amplify was primarily built for compatibility with Next.js [page components](https://nextjs.org/docs/basic-features/pages), and their [getServerSideProps data fetching mechanism](https://nextjs.org/docs/basic-features/data-fetching/get-server-side-props). Compatibility with other frameworks or Next.js features cannot be guaranteed. +SSR functionality in Amplify was primarily built for compatibility with Next.js [page components](https://nextjs.org/docs/pages/building-your-application/routing/pages-and-layouts), and their [getServerSideProps data fetching mechanism](https://nextjs.org/docs/basic-features/data-fetching/get-server-side-props). Compatibility with other frameworks or Next.js features cannot be guaranteed. - \ No newline at end of file + From 7a5727705c9344ee240f93bae1dbf4cad3787a12 Mon Sep 17 00:00:00 2001 From: James Jarvis Date: Wed, 27 Nov 2024 12:04:31 -0800 Subject: [PATCH 03/25] docs(js): more passwordless sign in examples (#8135) * more sign in examples * Apply suggestions from code review Co-authored-by: josef * fix typo --------- Co-authored-by: josef --- .../connect-your-frontend/sign-in/index.mdx | 111 +++++++++++++----- 1 file changed, 82 insertions(+), 29 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 78ffb2ea697..15272c691cd 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 @@ -1120,17 +1120,28 @@ Your application's users can also sign in using passwordless methods. To learn m -To request an OTP code via SMS for authentication, the challenge is passed as the challenge response to the confirm sign in API. +Pass `SMS_OTP` as the `preferredChallenge` 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 sign in next step: `CONFIRM_SIGN_IN_WITH_SMS_CODE`: ```ts -const { nextStep } = await confirmSignIn({ - challengeResponse: "SMS_OTP" +const { nextStep: signInNextStep } = await signIn({ + username: '+15551234567', + options: { + authFlowType: 'USER_AUTH', + preferredChallenge: 'SMS_OTP', + }, }); -// nextStep.signInStep === 'CONFIRM_SIGN_IN_WITH_SMS_CODE' -handleNextSignInStep(nextStep); +if (signInNextStep.signInStep === 'CONFIRM_SIGN_IN_WITH_SMS_CODE') { + // prompt user for otp code delivered via SMS + const { nextStep: confirmSignInNextStep } = await confirmSignIn({ + challengeResponse: '123456', + }); + + if (confirmSignInNextStep.signInStep === 'DONE') { + console.log('Sign in successful!'); + } +} ``` @@ -1223,17 +1234,27 @@ func confirmSignIn() -> AnyCancellable { -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`: +Pass `EMAIL_OTP` as the `preferredChallenge` when calling the `signIn` API in order to initiate a passwordless authentication flow using email OTP. ```ts -const { nextStep } = await confirmSignIn({ - challengeResponse: "EMAIL_OTP" +const { nextStep: signInNextStep } = await signIn({ + username: 'hello@example.com', + options: { + authFlowType: 'USER_AUTH', + preferredChallenge: 'EMAIL_OTP', + }, }); -// nextStep.signInStep === 'CONFIRM_SIGN_IN_WITH_EMAIL_CODE' -handleNextSignInStep(nextStep); +if (signInNextStep.signInStep === 'CONFIRM_SIGN_IN_WITH_EMAIL_CODE') { + // prompt user for otp code delivered via email + const { nextStep: confirmSignInNextStep } = await confirmSignIn({ + challengeResponse: '123456', + }); + + if (confirmSignInNextStep.signInStep === 'DONE') { + console.log('Sign in successful!'); + } +} ``` @@ -1326,15 +1347,20 @@ func confirmSignIn() -> AnyCancellable { -The WebAuthn credential flow is initiated by passing the challenge name to the confirm sign in api. +Pass `WEB_AUTHN` as the `preferredChallenge` in order to initiate the passwordless authentication flow using a WebAuthn credential. ```ts -const { nextStep } = await confirmSignIn({ - challengeResponse: "WEB_AUTHN", +const { nextStep: signInNextStep } = await signIn({ + username: 'hello@example.com', + options: { + authFlowType: 'USER_AUTH', + preferredChallenge: 'WEB_AUTHN', + }, }); -// nextStep.signInStep === 'DONE' -handleNextSignInStep(nextStep); +if (signInNextStep.signInStep === 'DONE') { + console.log('Sign in successful!'); +} ``` @@ -1356,25 +1382,52 @@ handleNextSignInStep(nextStep); -### Password or SRP +### Password -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. + Pass either `PASSWORD` or `PASSWORD_SRP` as the `preferredChallenge` in order to initiate a traditional password based authentication flow. ```ts -const { nextStep } = await confirmSignIn({ - challengeResponse: "PASSWORD_SRP", // or "PASSWORD" +const { nextStep: signInNextStep } = await signIn({ + username: 'hello@example.com', + password: 'example-password', + options: { + authFlowType: 'USER_AUTH', + preferredChallenge: 'PASSWORD_SRP', // or 'PASSWORD' + }, }); -// in both cases -// nextStep.signInStep === 'CONFIRM_SIGN_IN_WITH_PASSWORD' -handleNextSignInStep(nextStep); +if (confirmSignInNextStep.signInStep === 'DONE') { + console.log('Sign in successful!'); +} +``` + -const { nextStep: nextNextStep } = await confirmSignIn({ - challengeResponse: "Test123#", +### First Factor Selection + +Omit the `preferredChallenge` parameter to discover what first factors are available for a given user. + +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', + options: { + authFlowType: 'USER_AUTH', + }, }); -// nextNextStep.signInStep === 'DONE' -handleNextSignInStep(nextNextStep); +if ( + signInNextStep.signInStep === 'CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION' +) { + // present user with list of available challenges + console.log(`Available Challenges: ${signInNextStep.availableChallenges}`); + + // respond with user selection using `confirmSignIn` API + const { nextStep: nextConfirmSignInStep } = await confirmSignIn({ + challengeResponse: 'SMS_OTP', // or 'EMAIL_OTP', 'WEB_AUTHN', 'PASSWORD', 'PASSWORD_SRP' + }); +} + ``` From 99e934b50ca45c9d8082ac7b368be2ef6877ded5 Mon Sep 17 00:00:00 2001 From: Kethan sai Date: Sun, 1 Dec 2024 00:41:26 -0500 Subject: [PATCH 04/25] update nextjs AI generate sample (#8143) --- src/pages/[platform]/ai/set-up-ai/index.mdx | 27 ++++++++++++++++++--- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/src/pages/[platform]/ai/set-up-ai/index.mdx b/src/pages/[platform]/ai/set-up-ai/index.mdx index 83590478d79..7526472567a 100644 --- a/src/pages/[platform]/ai/set-up-ai/index.mdx +++ b/src/pages/[platform]/ai/set-up-ai/index.mdx @@ -381,8 +381,17 @@ export default function App() { ```tsx title="pages/index.tsx" -import { Flex, TextAreaField, Loader, Text, View } from "@aws-amplify/ui-react" -import { useAIConversation } from "@/client"; +import { useAIGeneration } from "@/client"; +import { + Button, + Flex, + Heading, + Loader, + Text, + TextAreaField, + View, +} from "@aws-amplify/ui-react"; +import React from "react"; export default function Page() { const [description, setDescription] = React.useState(""); @@ -429,8 +438,18 @@ export default function Page() { ```tsx title="app/page.tsx" -'use client' -import { useAIConversation } from "@/client"; +"use client"; +import { useAIGeneration } from "@/client"; +import { + Button, + Flex, + Heading, + Loader, + Text, + TextAreaField, + View, +} from "@aws-amplify/ui-react"; +import React from "react"; export default function Page() { const [description, setDescription] = React.useState(""); From 64403cfd589df924ba29cecae3e3299d4836c9db Mon Sep 17 00:00:00 2001 From: Vincent Tran Date: Mon, 2 Dec 2024 10:24:51 -0800 Subject: [PATCH 05/25] Update passwordless docs with Android examples (#8139) * Add initial android sign in doc updates * Add sign in with preferred challenge * Finish adding the non-webauthn sign in docs * Add passwordless sign in docs into the multi-step-sign-in page * Update manage-webauthn-credentials/index.mdx for android * Add callingActivity to associateWebAuthnCredential examples * Bring android sign-in docs to parity * Add Android docs for switching authentication flows * Add USER_AUTH password to the sign in docs * Update sign up docs * Add Android's WebAuthn details to `connect-your-frontend/sign-in/index.mdx` * Fix some small issues and update multi-step sign in * Add first factor selection section for android after merge * Update src/pages/[platform]/build-a-backend/auth/connect-your-frontend/multi-step-sign-in/index.mdx Co-authored-by: josef * Update src/pages/[platform]/build-a-backend/auth/connect-your-frontend/multi-step-sign-in/index.mdx Co-authored-by: josef * Apply suggestions from code review Co-authored-by: josef * Replace usages of username with email * Update src/pages/[platform]/build-a-backend/auth/connect-your-frontend/sign-in/index.mdx Co-authored-by: James Jarvis * Update index.mdx --------- Co-authored-by: Matt Creaser Co-authored-by: josef Co-authored-by: James Jarvis --- cspell.json | 3 +- .../multi-step-sign-in/index.mdx | 672 ++++++++++-------- .../connect-your-frontend/sign-in/index.mdx | 569 ++++++++++++++- .../connect-your-frontend/sign-up/index.mdx | 444 +++++++++++- .../switching-authentication-flows/index.mdx | 410 ++++++++++- .../manage-webauthn-credentials/index.mdx | 187 ++++- 6 files changed, 1916 insertions(+), 369 deletions(-) diff --git a/cspell.json b/cspell.json index 8aa9138f087..c2c0df0f66b 100644 --- a/cspell.json +++ b/cspell.json @@ -1616,7 +1616,8 @@ "jamba", "webauthn", "knowledgebases", - "rehype" + "rehype", + "assetlinks" ], "flagWords": ["hte", "full-stack", "Full-stack", "Full-Stack", "sudo"], "patterns": [ 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 4604f7441da..d19dc10329e 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 @@ -1067,102 +1067,111 @@ The `nextStep` property is of enum type `AuthSignInStep`. Depending on its value ```java try { - AWSCognitoAuthSignInOptions options = AWSCognitoAuthSignInOptions.builder().authFlowType(AuthFlowType.USER_SRP_AUTH).build(); - Amplify.Auth.signIn( - "username", - "password", - options, - result -> - { - AuthNextSignInStep nextStep = result.getNextStep(); - switch (nextStep.getSignInStep()) { - case CONFIRM_SIGN_IN_WITH_TOTP_CODE: { - Log.i("AuthQuickstart", "Received next step as confirm sign in with TOTP code"); - // Prompt the user to enter the TOTP code generated in their authenticator app - // Then invoke `confirmSignIn` api with the code - break; - } - case CONTINUE_SIGN_IN_WITH_MFA_SETUP_SELECTION: { - Log.i("AuthQuickstart", "Received next step as continue sign in by selecting an MFA method to setup"); - Log.i("AuthQuickstart", "Allowed MFA types for setup" + nextStep.getAllowedMFATypes()); - // Prompt the user to select the MFA type they want to setup - // Then invoke `confirmSignIn` api with the MFA type - break; - } - case CONTINUE_SIGN_IN_WITH_EMAIL_MFA_SETUP: { - Log.i("AuthQuickstart", "Received next step as continue sign in by setting up email MFA"); - // Prompt the user to enter the email address they would like to use to receive OTPs - // Then invoke `confirmSignIn` api with the email address - break; - } - case CONTINUE_SIGN_IN_WITH_TOTP_SETUP: { - Log.i("AuthQuickstart", "Received next step as continue sign in by setting up TOTP"); - Log.i("AuthQuickstart", "Shared secret that will be used to set up TOTP in the authenticator app" + nextStep.getTotpSetupDetails().getSharedSecret()); - // Prompt the user to enter the TOTP code generated in their authenticator app - // Then invoke `confirmSignIn` api with the code - break; - } - case CONTINUE_SIGN_IN_WITH_MFA_SELECTION: { - Log.i("AuthQuickstart", "Received next step as continue sign in by selecting MFA type"); - Log.i("AuthQuickstart", "Allowed MFA type" + nextStep.getAllowedMFATypes()); - // Prompt the user to select the MFA type they want to use - // Then invoke `confirmSignIn` api with the MFA type - break; - } - case CONFIRM_SIGN_IN_WITH_SMS_MFA_CODE: { - Log.i("AuthQuickstart", "SMS code sent to " + nextStep.getCodeDeliveryDetails().getDestination()); - Log.i("AuthQuickstart", "Additional Info :" + nextStep.getAdditionalInfo()); - // Prompt the user to enter the SMS MFA code they received - // Then invoke `confirmSignIn` api with the code - break; - } - case CONFIRM_SIGN_IN_WITH_OTP: { - Log.i("AuthQuickstart", "OTP code sent to " + nextStep.getCodeDeliveryDetails().getDestination()); - Log.i("AuthQuickstart", "Additional Info :" + nextStep.getAdditionalInfo()); - // Prompt the user to enter the OTP MFA code they received - // Then invoke `confirmSignIn` api with the code - break; - } - case CONFIRM_SIGN_IN_WITH_CUSTOM_CHALLENGE: { - Log.i("AuthQuickstart", "Custom challenge, additional info: " + nextStep.getAdditionalInfo()); - // Prompt the user to enter custom challenge answer - // Then invoke `confirmSignIn` api with the answer - break; - } - case CONFIRM_SIGN_IN_WITH_NEW_PASSWORD: { - Log.i("AuthQuickstart", "Sign in with new password, additional info: " + nextStep.getAdditionalInfo()); - // Prompt the user to enter a new password - // Then invoke `confirmSignIn` api with new password - break; - } - case DONE: { - Log.i("AuthQuickstart", "SignIn complete"); - // User has successfully signed in to the app - break; - } - } - }, - error -> { - if (error instanceof UserNotConfirmedException) { - // User was not confirmed during the signup process. - // Invoke `confirmSignUp` api to confirm the user if - // they have the confirmation code. If they do not have the - // confirmation code, invoke `resendSignUpCode` to send the - // code again. - // After the user is confirmed, invoke the `signIn` api again. - Log.i("AuthQuickstart", "Signup confirmation required" + error); - } else if (error instanceof PasswordResetRequiredException) { - // User needs to reset their password. - // Invoke `resetPassword` api to start the reset password - // flow, and once reset password flow completes, invoke - // `signIn` api to trigger signIn flow again. - Log.i("AuthQuickstart", "Password reset required" + error); - } else { - Log.e("AuthQuickstart", "SignIn failed: " + error); - } - } - - ); + Amplify.Auth.signIn( + "hello@example.com", + "password", + result -> + { + AuthNextSignInStep nextStep = result.getNextStep(); + switch (nextStep.getSignInStep()) { + case CONFIRM_SIGN_IN_WITH_TOTP_CODE: { + Log.i("AuthQuickstart", "Received next step as confirm sign in with TOTP code"); + // Prompt the user to enter the TOTP code generated in their authenticator app + // Then invoke `confirmSignIn` api with the code + break; + } + case CONTINUE_SIGN_IN_WITH_MFA_SETUP_SELECTION: { + Log.i("AuthQuickstart", "Received next step as continue sign in by selecting an MFA method to setup"); + Log.i("AuthQuickstart", "Allowed MFA types for setup" + nextStep.getAllowedMFATypes()); + // Prompt the user to select the MFA type they want to setup + // Then invoke `confirmSignIn` api with the MFA type + break; + } + case CONTINUE_SIGN_IN_WITH_EMAIL_MFA_SETUP: { + Log.i("AuthQuickstart", "Received next step as continue sign in by setting up email MFA"); + // Prompt the user to enter the email address they would like to use to receive OTPs + // Then invoke `confirmSignIn` api with the email address + break; + } + case CONTINUE_SIGN_IN_WITH_TOTP_SETUP: { + Log.i("AuthQuickstart", "Received next step as continue sign in by setting up TOTP"); + Log.i("AuthQuickstart", "Shared secret that will be used to set up TOTP in the authenticator app" + nextStep.getTotpSetupDetails().getSharedSecret()); + // Prompt the user to enter the TOTP code generated in their authenticator app + // Then invoke `confirmSignIn` api with the code + break; + } + case CONTINUE_SIGN_IN_WITH_MFA_SELECTION: { + Log.i("AuthQuickstart", "Received next step as continue sign in by selecting MFA type"); + Log.i("AuthQuickstart", "Allowed MFA type" + nextStep.getAllowedMFATypes()); + // Prompt the user to select the MFA type they want to use + // Then invoke `confirmSignIn` api with the MFA type + break; + } + case CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION: { + Log.i("AuthQuickstart", "Available authentication factors for this user: " + result.getNextStep().getAvailableFactors()); + // Prompt the user to select which authentication factor they want to use to sign-in + // Then invoke `confirmSignIn` api with that selection + break; + } + case CONFIRM_SIGN_IN_WITH_SMS_MFA_CODE: { + Log.i("AuthQuickstart", "SMS code sent to " + nextStep.getCodeDeliveryDetails().getDestination()); + Log.i("AuthQuickstart", "Additional Info :" + nextStep.getAdditionalInfo()); + // Prompt the user to enter the SMS MFA code they received + // Then invoke `confirmSignIn` api with the code + break; + } + case CONFIRM_SIGN_IN_WITH_OTP: { + Log.i("AuthQuickstart", "OTP code sent to " + nextStep.getCodeDeliveryDetails().getDestination()); + Log.i("AuthQuickstart", "Additional Info :" + nextStep.getAdditionalInfo()); + // Prompt the user to enter the OTP MFA code they received + // Then invoke `confirmSignIn` api with the code + break; + } + case CONFIRM_SIGN_IN_WITH_PASSWORD: { + Log.i("AuthQuickstart", "Received next step as confirm sign in with password"); + // Prompt the user to enter their password + // Then invoke `confirmSignIn` api with that password + break; + } + case CONFIRM_SIGN_IN_WITH_CUSTOM_CHALLENGE: { + Log.i("AuthQuickstart", "Custom challenge, additional info: " + nextStep.getAdditionalInfo()); + // Prompt the user to enter custom challenge answer + // Then invoke `confirmSignIn` api with the answer + break; + } + case CONFIRM_SIGN_IN_WITH_NEW_PASSWORD: { + Log.i("AuthQuickstart", "Sign in with new password, additional info: " + nextStep.getAdditionalInfo()); + // Prompt the user to enter a new password + // Then invoke `confirmSignIn` api with new password + break; + } + case DONE: { + Log.i("AuthQuickstart", "SignIn complete"); + // User has successfully signed in to the app + break; + } + } + }, + error -> { + if (error instanceof UserNotConfirmedException) { + // User was not confirmed during the signup process. + // Invoke `confirmSignUp` api to confirm the user if + // they have the confirmation code. If they do not have the + // confirmation code, invoke `resendSignUpCode` to send the + // code again. + // After the user is confirmed, invoke the `signIn` api again. + Log.i("AuthQuickstart", "Signup confirmation required" + error); + } else if (error instanceof PasswordResetRequiredException) { + // User needs to reset their password. + // Invoke `resetPassword` api to start the reset password + // flow, and once reset password flow completes, invoke + // `signIn` api to trigger signIn flow again. + Log.i("AuthQuickstart", "Password reset required" + error); + } else { + Log.e("AuthQuickstart", "SignIn failed: " + error); + } + } + ); } catch (Exception error) { Log.e("AuthQuickstart", "Unexpected error occurred: " + error); } @@ -1173,91 +1182,103 @@ try { ```kotlin -val options = AWSCognitoAuthSignInOptions.builder().authFlowType(AuthFlowType.USER_SRP_AUTH).build() try { - Amplify.Auth.signIn( - "username", - "password", - options, - { result -> - val nextStep = result.nextStep - when(nextStep.signInStep){ - AuthSignInStep.CONFIRM_SIGN_IN_WITH_TOTP_CODE -> { - Log.i("AuthQuickstart", "Received next step as confirm sign in with TOTP code") - // Prompt the user to enter the TOTP code generated in their authenticator app - // Then invoke `confirmSignIn` api with the code - } - AuthSignInStep.CONTINUE_SIGN_IN_WITH_MFA_SETUP_SELECTION -> { - Log.i("AuthQuickstart", "Received next step as continue sign in by selecting an MFA method to setup"); - Log.i("AuthQuickstart", "Allowed MFA types for setup" + nextStep.getAllowedMFATypes()); - // Prompt the user to select the MFA type they want to setup - // Then invoke `confirmSignIn` api with the MFA type - } - AuthSignInStep.CONTINUE_SIGN_IN_WITH_EMAIL_MFA_SETUP -> { - Log.i("AuthQuickstart", "Received next step as continue sign in by setting up email MFA"); - // Prompt the user to enter the email address they would like to use to receive OTPs - // Then invoke `confirmSignIn` api with the email address - } - AuthSignInStep.CONTINUE_SIGN_IN_WITH_TOTP_SETUP -> { - Log.i("AuthQuickstart", "Received next step as continue sign in by setting up TOTP") - Log.i("AuthQuickstart", "Shared secret that will be used to set up TOTP in the authenticator app ${nextStep.totpSetupDetails.sharedSecret}") - // Prompt the user to enter the TOTP code generated in their authenticator app - // Then invoke `confirmSignIn` api with the code - } - AuthSignInStep.CONTINUE_SIGN_IN_WITH_MFA_SELECTION -> { - Log.i("AuthQuickstart", "Received next step as continue sign in by selecting MFA type") - Log.i("AuthQuickstart", "Allowed MFA types ${nextStep.allowedMFATypes}") - // Prompt the user to select the MFA type they want to use - // Then invoke `confirmSignIn` api with the MFA type - } - AuthSignInStep.CONFIRM_SIGN_IN_WITH_SMS_MFA_CODE -> { - Log.i("AuthQuickstart", "SMS code sent to ${nextStep.codeDeliveryDetails?.destination}") - Log.i("AuthQuickstart", "Additional Info ${nextStep.additionalInfo}") - // Prompt the user to enter the SMS MFA code they received - // Then invoke `confirmSignIn` api with the code - } - AuthSignInStep.CONFIRM_SIGN_IN_WITH_OTP -> { - Log.i("AuthQuickstart", "OTP code sent to " + nextStep.getCodeDeliveryDetails().getDestination()); - Log.i("AuthQuickstart", "Additional Info :" + nextStep.getAdditionalInfo()); - // Prompt the user to enter the OTP MFA code they received - // Then invoke `confirmSignIn` api with the code - } - AuthSignInStep.CONFIRM_SIGN_IN_WITH_CUSTOM_CHALLENGE -> { - Log.i("AuthQuickstart","Custom challenge, additional info: ${nextStep.additionalInfo}") - // Prompt the user to enter custom challenge answer - // Then invoke `confirmSignIn` api with the answer - } - AuthSignInStep.CONFIRM_SIGN_IN_WITH_NEW_PASSWORD -> { - Log.i("AuthQuickstart", "Sign in with new password, additional info: ${nextStep.additionalInfo}") - // Prompt the user to enter a new password - // Then invoke `confirmSignIn` api with new password - } - AuthSignInStep.DONE -> { - Log.i("AuthQuickstart", "SignIn complete") - // User has successfully signed in to the app - } - } + Amplify.Auth.signIn( + "hello@example.com", + "password", + { result -> + val nextStep = result.nextStep + when(nextStep.signInStep){ + AuthSignInStep.CONFIRM_SIGN_IN_WITH_TOTP_CODE -> { + Log.i("AuthQuickstart", "Received next step as confirm sign in with TOTP code") + // Prompt the user to enter the TOTP code generated in their authenticator app + // Then invoke `confirmSignIn` api with the code + } + AuthSignInStep.CONTINUE_SIGN_IN_WITH_MFA_SETUP_SELECTION -> { + Log.i("AuthQuickstart", "Received next step as continue sign in by selecting an MFA method to setup") + Log.i("AuthQuickstart", "Allowed MFA types for setup ${nextStep.allowedMFATypes}") + // Prompt the user to select the MFA type they want to setup + // Then invoke `confirmSignIn` api with the MFA type + } + AuthSignInStep.CONTINUE_SIGN_IN_WITH_EMAIL_MFA_SETUP -> { + Log.i("AuthQuickstart", "Received next step as continue sign in by setting up email MFA") + // Prompt the user to enter the email address they would like to use to receive OTPs + // Then invoke `confirmSignIn` api with the email address + } + AuthSignInStep.CONTINUE_SIGN_IN_WITH_TOTP_SETUP -> { + Log.i("AuthQuickstart", "Received next step as continue sign in by setting up TOTP") + Log.i("AuthQuickstart", "Shared secret that will be used to set up TOTP in the authenticator app ${nextStep.totpSetupDetails?.sharedSecret}") + // Prompt the user to enter the TOTP code generated in their authenticator app + // Then invoke `confirmSignIn` api with the code + } + AuthSignInStep.CONTINUE_SIGN_IN_WITH_MFA_SELECTION -> { + Log.i("AuthQuickstart", "Received next step as continue sign in by selecting MFA type") + Log.i("AuthQuickstart", "Allowed MFA types ${nextStep.allowedMFATypes}") + // Prompt the user to select the MFA type they want to use + // Then invoke `confirmSignIn` api with the MFA type + } + AuthSignInStep.CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION -> { + Log.i("AuthQuickstart", "Available authentication factors for this user: ${result.nextStep.availableFactors}") + // Prompt the user to select which authentication factor they want to use to sign-in + // Then invoke `confirmSignIn` api with that selection + } + AuthSignInStep.CONFIRM_SIGN_IN_WITH_SMS_MFA_CODE -> { + Log.i("AuthQuickstart", "SMS code sent to ${nextStep.codeDeliveryDetails?.destination}") + Log.i("AuthQuickstart", "Additional Info ${nextStep.additionalInfo}") + // Prompt the user to enter the SMS MFA code they received + // Then invoke `confirmSignIn` api with the code + } + AuthSignInStep.CONFIRM_SIGN_IN_WITH_OTP -> { + Log.i("AuthQuickstart", "OTP code sent to ${nextStep.codeDeliveryDetails?.destination}") + Log.i("AuthQuickstart", "Additional Info ${nextStep.additionalInfo}") + // Prompt the user to enter the OTP MFA code they received + // Then invoke `confirmSignIn` api with the code + } + AuthSignInStep.CONFIRM_SIGN_IN_WITH_PASSWORD -> { + Log.i("AuthQuickstart", "Received next step as confirm sign in with password") + // Prompt the user to enter their password + // Then invoke `confirmSignIn` api with that password + } + AuthSignInStep.CONFIRM_SIGN_IN_WITH_CUSTOM_CHALLENGE -> { + Log.i("AuthQuickstart","Custom challenge, additional info: ${nextStep.additionalInfo}") + // Prompt the user to enter custom challenge answer + // Then invoke `confirmSignIn` api with the answer + } + AuthSignInStep.CONFIRM_SIGN_IN_WITH_NEW_PASSWORD -> { + Log.i("AuthQuickstart", "Sign in with new password, additional info: ${nextStep.additionalInfo}") + // Prompt the user to enter a new password + // Then invoke `confirmSignIn` api with new password + } + AuthSignInStep.DONE -> { + Log.i("AuthQuickstart", "SignIn complete") + // User has successfully signed in to the app + } + } - } - ) { error -> - if (error is UserNotConfirmedException) { - // User was not confirmed during the signup process. - // Invoke `confirmSignUp` api to confirm the user if - // they have the confirmation code. If they do not have the - // confirmation code, invoke `resendSignUpCode` to send the - // code again. - // After the user is confirmed, invoke the `signIn` api again. - Log.i("AuthQuickstart", "Signup confirmation required", error) - } else if (error is PasswordResetRequiredException) { - // User needs to reset their password. - // Invoke `resetPassword` api to start the reset password - // flow, and once reset password flow completes, invoke - // `signIn` api to trigger signIn flow again. - Log.i("AuthQuickstart", "Password reset required", error) - } else { - Log.e("AuthQuickstart", "Unexpected error occurred: $error") - } - } + } + ) { error -> + when (error) { + is UserNotConfirmedException -> { + // User was not confirmed during the signup process. + // Invoke `confirmSignUp` api to confirm the user if + // they have the confirmation code. If they do not have the + // confirmation code, invoke `resendSignUpCode` to send the + // code again. + // After the user is confirmed, invoke the `signIn` api again. + Log.e("AuthQuickstart", "Signup confirmation required", error) + } + is PasswordResetRequiredException -> { + // User needs to reset their password. + // Invoke `resetPassword` api to start the reset password + // flow, and once reset password flow completes, invoke + // `signIn` api to trigger signIn flow again. + Log.e("AuthQuickstart", "Password reset required", error) + } + else -> { + Log.e("AuthQuickstart", "Unexpected error occurred: $error") + } + } + } } catch (error: Exception) { Log.e("AuthQuickstart", "Unexpected error occurred: $error") } @@ -1268,13 +1289,10 @@ try { ```kotlin -val options = - AWSCognitoAuthSignInOptions.builder().authFlowType(AuthFlowType.USER_SRP_AUTH).build() try { val result = Amplify.Auth.signIn( - "username", - "password", - options + "hello@example.com", + "password" ) val nextStep = result.nextStep when (nextStep.signInStep) { @@ -1284,19 +1302,19 @@ try { // Then invoke `confirmSignIn` api with the code } AuthSignInStep.CONTINUE_SIGN_IN_WITH_MFA_SETUP_SELECTION -> { - Log.i("AuthQuickstart", "Received next step as continue sign in by selecting an MFA method to setup"); - Log.i("AuthQuickstart", "Allowed MFA types for setup" + nextStep.getAllowedMFATypes()); + Log.i("AuthQuickstart", "Received next step as continue sign in by selecting an MFA method to setup") + Log.i("AuthQuickstart", "Allowed MFA types for setup ${nextStep.allowedMFATypes}") // Prompt the user to select the MFA type they want to setup // Then invoke `confirmSignIn` api with the MFA type } AuthSignInStep.CONTINUE_SIGN_IN_WITH_EMAIL_MFA_SETUP -> { - Log.i("AuthQuickstart", "Received next step as continue sign in by setting up email MFA"); + Log.i("AuthQuickstart", "Received next step as continue sign in by setting up email MFA") // Prompt the user to enter the email address they would like to use to receive OTPs // Then invoke `confirmSignIn` api with the email address } AuthSignInStep.CONTINUE_SIGN_IN_WITH_TOTP_SETUP -> { Log.i("AuthQuickstart", "Received next step as continue sign in by setting up TOTP") - Log.i("AuthQuickstart", "Shared secret that will be used to set up TOTP in the authenticator app ${nextStep.totpSetupDetails.sharedSecret}") + Log.i("AuthQuickstart", "Shared secret that will be used to set up TOTP in the authenticator app ${nextStep.totpSetupDetails?.sharedSecret}") // Prompt the user to enter the TOTP code generated in their authenticator app // Then invoke `confirmSignIn` api with the code } @@ -1306,6 +1324,11 @@ try { // Prompt the user to select the MFA type they want to use // Then invoke `confirmSignIn` api with the MFA type } + AuthSignInStep.CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION -> { + Log.i("AuthQuickstart", "Available authentication factors for this user: ${result.nextStep.availableFactors}") + // Prompt the user to select which authentication factor they want to use to sign-in + // Then invoke `confirmSignIn` api with that selection + } AuthSignInStep.CONFIRM_SIGN_IN_WITH_SMS_MFA_CODE -> { Log.i("AuthQuickstart", "SMS code sent to ${nextStep.codeDeliveryDetails?.destination}") Log.i("AuthQuickstart", "Additional Info ${nextStep.additionalInfo}") @@ -1318,16 +1341,18 @@ try { // Prompt the user to enter the OTP MFA code they received // Then invoke `confirmSignIn` api with the code } + AuthSignInStep.CONFIRM_SIGN_IN_WITH_PASSWORD -> { + Log.i("AuthQuickstart", "Received next step as confirm sign in with password") + // Prompt the user to enter their password + // Then invoke `confirmSignIn` api with that password + } AuthSignInStep.CONFIRM_SIGN_IN_WITH_CUSTOM_CHALLENGE -> { - Log.i("AuthQuickstart", "Custom challenge, additional info: ${nextStep.additionalInfo}") + Log.i("AuthQuickstart","Custom challenge, additional info: ${nextStep.additionalInfo}") // Prompt the user to enter custom challenge answer // Then invoke `confirmSignIn` api with the answer } AuthSignInStep.CONFIRM_SIGN_IN_WITH_NEW_PASSWORD -> { - Log.i( - "AuthQuickstart", - "Sign in with new password, additional info: ${nextStep.additionalInfo}" - ) + Log.i("AuthQuickstart", "Sign in with new password, additional info: ${nextStep.additionalInfo}") // Prompt the user to enter a new password // Then invoke `confirmSignIn` api with new password } @@ -1337,23 +1362,27 @@ try { } } } catch (error: Exception) { - if (error is UserNotConfirmedException) { - // User was not confirmed during the signup process. - // Invoke `confirmSignUp` api to confirm the user if - // they have the confirmation code. If they do not have the - // confirmation code, invoke `resendSignUpCode` to send the - // code again. - // After the user is confirmed, invoke the `signIn` api again. - Log.i("AuthQuickstart", "Signup confirmation required", error) - } else if (error is PasswordResetRequiredException) { - // User needs to reset their password. - // Invoke `resetPassword` api to start the reset password - // flow, and once reset password flow completes, invoke - // `signIn` api to trigger signIn flow again. - Log.i("AuthQuickstart", "Password reset required", error) - } else { - Log.e("AuthQuickstart", "Unexpected error occurred: $error") - } + when (error) { + is UserNotConfirmedException -> { + // User was not confirmed during the signup process. + // Invoke `confirmSignUp` api to confirm the user if + // they have the confirmation code. If they do not have the + // confirmation code, invoke `resendSignUpCode` to send the + // code again. + // After the user is confirmed, invoke the `signIn` api again. + Log.e("AuthQuickstart", "Signup confirmation required", error) + } + is PasswordResetRequiredException -> { + // User needs to reset their password. + // Invoke `resetPassword` api to start the reset password + // flow, and once reset password flow completes, invoke + // `signIn` api to trigger signIn flow again. + Log.e("AuthQuickstart", "Password reset required", error) + } + else -> { + Log.e("AuthQuickstart", "Unexpected error occurred: $error") + } + } } ``` @@ -1362,98 +1391,108 @@ try { ```java - -AWSCognitoAuthSignInOptions options = AWSCognitoAuthSignInOptions.builder().authFlowType(AuthFlowType.USER_SRP_AUTH).build(); -RxAmplify.Auth.signIn("username", "password", options).subscribe( - result -> - { - AuthNextSignInStep nextStep = result.getNextStep(); - switch (nextStep.getSignInStep()) { - case CONFIRM_SIGN_IN_WITH_TOTP_CODE: { - Log.i("AuthQuickstart", "Received next step as confirm sign in with TOTP code"); - // Prompt the user to enter the TOTP code generated in their authenticator app - // Then invoke `confirmSignIn` api with the code - break; - } - case CONTINUE_SIGN_IN_WITH_MFA_SETUP_SELECTION: { - Log.i("AuthQuickstart", "Received next step as continue sign in by selecting an MFA method to setup"); - Log.i("AuthQuickstart", "Allowed MFA types for setup" + nextStep.getAllowedMFATypes()); - // Prompt the user to select the MFA type they want to setup - // Then invoke `confirmSignIn` api with the MFA type - break; - } - case CONTINUE_SIGN_IN_WITH_EMAIL_MFA_SETUP: { - Log.i("AuthQuickstart", "Received next step as continue sign in by setting up email MFA"); - // Prompt the user to enter the email address they would like to use to receive OTPs - // Then invoke `confirmSignIn` api with the email address - break; - } - case CONTINUE_SIGN_IN_WITH_TOTP_SETUP: { - Log.i("AuthQuickstart", "Received next step as continue sign in by setting up TOTP"); - Log.i("AuthQuickstart", "Shared secret that will be used to set up TOTP in the authenticator app" + nextStep.getTotpSetupDetails().getSharedSecret()); - // Prompt the user to enter the TOTP code generated in their authenticator app - // Then invoke `confirmSignIn` api with the code - break; - } - case CONTINUE_SIGN_IN_WITH_MFA_SELECTION: { - Log.i("AuthQuickstart", "Received next step as continue sign in by selecting MFA type"); - Log.i("AuthQuickstart", "Allowed MFA type" + nextStep.getAllowedMFATypes()); - // Prompt the user to select the MFA type they want to use - // Then invoke `confirmSignIn` api with the MFA type - break; - } - case CONFIRM_SIGN_IN_WITH_SMS_MFA_CODE: { - Log.i("AuthQuickstart", "SMS code sent to " + nextStep.getCodeDeliveryDetails().getDestination()); - Log.i("AuthQuickstart", "Additional Info :" + nextStep.getAdditionalInfo()); - // Prompt the user to enter the SMS MFA code they received - // Then invoke `confirmSignIn` api with the code - break; - } - case CONFIRM_SIGN_IN_WITH_OTP: { - Log.i("AuthQuickstart", "OTP code sent to " + nextStep.getCodeDeliveryDetails().getDestination()); - Log.i("AuthQuickstart", "Additional Info :" + nextStep.getAdditionalInfo()); - // Prompt the user to enter the OTP MFA code they received - // Then invoke `confirmSignIn` api with the code - break; - } - case CONFIRM_SIGN_IN_WITH_CUSTOM_CHALLENGE: { - Log.i("AuthQuickstart", "Custom challenge, additional info: " + nextStep.getAdditionalInfo()); - // Prompt the user to enter custom challenge answer - // Then invoke `confirmSignIn` api with the answer - break; - } - case CONFIRM_SIGN_IN_WITH_NEW_PASSWORD: { - Log.i("AuthQuickstart", "Sign in with new password, additional info: " + nextStep.getAdditionalInfo()); - // Prompt the user to enter a new password - // Then invoke `confirmSignIn` api with new password - break; - } - case DONE: { - Log.i("AuthQuickstart", "SignIn complete"); - // User has successfully signed in to the app - break; - } +RxAmplify.Auth.signIn("hello@example.com", "password").subscribe( + result -> + { + AuthNextSignInStep nextStep = result.getNextStep(); + switch (nextStep.getSignInStep()) { + case CONFIRM_SIGN_IN_WITH_TOTP_CODE: { + Log.i("AuthQuickstart", "Received next step as confirm sign in with TOTP code"); + // Prompt the user to enter the TOTP code generated in their authenticator app + // Then invoke `confirmSignIn` api with the code + break; + } + case CONTINUE_SIGN_IN_WITH_MFA_SETUP_SELECTION: { + Log.i("AuthQuickstart", "Received next step as continue sign in by selecting an MFA method to setup"); + Log.i("AuthQuickstart", "Allowed MFA types for setup" + nextStep.getAllowedMFATypes()); + // Prompt the user to select the MFA type they want to setup + // Then invoke `confirmSignIn` api with the MFA type + break; + } + case CONTINUE_SIGN_IN_WITH_EMAIL_MFA_SETUP: { + Log.i("AuthQuickstart", "Received next step as continue sign in by setting up email MFA"); + // Prompt the user to enter the email address they would like to use to receive OTPs + // Then invoke `confirmSignIn` api with the email address + break; + } + case CONTINUE_SIGN_IN_WITH_TOTP_SETUP: { + Log.i("AuthQuickstart", "Received next step as continue sign in by setting up TOTP"); + Log.i("AuthQuickstart", "Shared secret that will be used to set up TOTP in the authenticator app" + nextStep.getTotpSetupDetails().getSharedSecret()); + // Prompt the user to enter the TOTP code generated in their authenticator app + // Then invoke `confirmSignIn` api with the code + break; + } + case CONTINUE_SIGN_IN_WITH_MFA_SELECTION: { + Log.i("AuthQuickstart", "Received next step as continue sign in by selecting MFA type"); + Log.i("AuthQuickstart", "Allowed MFA type" + nextStep.getAllowedMFATypes()); + // Prompt the user to select the MFA type they want to use + // Then invoke `confirmSignIn` api with the MFA type + break; + } + case CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION: { + Log.i("AuthQuickstart", "Available authentication factors for this user: " + result.getNextStep().getAvailableFactors()); + // Prompt the user to select which authentication factor they want to use to sign-in + // Then invoke `confirmSignIn` api with that selection + break; + } + case CONFIRM_SIGN_IN_WITH_SMS_MFA_CODE: { + Log.i("AuthQuickstart", "SMS code sent to " + nextStep.getCodeDeliveryDetails().getDestination()); + Log.i("AuthQuickstart", "Additional Info :" + nextStep.getAdditionalInfo()); + // Prompt the user to enter the SMS MFA code they received + // Then invoke `confirmSignIn` api with the code + break; + } + case CONFIRM_SIGN_IN_WITH_OTP: { + Log.i("AuthQuickstart", "OTP code sent to " + nextStep.getCodeDeliveryDetails().getDestination()); + Log.i("AuthQuickstart", "Additional Info :" + nextStep.getAdditionalInfo()); + // Prompt the user to enter the OTP MFA code they received + // Then invoke `confirmSignIn` api with the code + break; + } + case CONFIRM_SIGN_IN_WITH_PASSWORD: { + Log.i("AuthQuickstart", "Received next step as confirm sign in with password"); + // Prompt the user to enter their password + // Then invoke `confirmSignIn` api with that password + break; + } + case CONFIRM_SIGN_IN_WITH_CUSTOM_CHALLENGE: { + Log.i("AuthQuickstart", "Custom challenge, additional info: " + nextStep.getAdditionalInfo()); + // Prompt the user to enter custom challenge answer + // Then invoke `confirmSignIn` api with the answer + break; + } + case CONFIRM_SIGN_IN_WITH_NEW_PASSWORD: { + Log.i("AuthQuickstart", "Sign in with new password, additional info: " + nextStep.getAdditionalInfo()); + // Prompt the user to enter a new password + // Then invoke `confirmSignIn` api with new password + break; + } + case DONE: { + Log.i("AuthQuickstart", "SignIn complete"); + // User has successfully signed in to the app + break; } - }, - error -> { - if (error instanceof UserNotConfirmedException) { - // User was not confirmed during the signup process. - // Invoke `confirmSignUp` api to confirm the user if - // they have the confirmation code. If they do not have the - // confirmation code, invoke `resendSignUpCode` to send the - // code again. - // After the user is confirmed, invoke the `signIn` api again. - Log.i("AuthQuickstart", "Signup confirmation required" + error); - } else if (error instanceof PasswordResetRequiredException) { - // User needs to reset their password. - // Invoke `resetPassword` api to start the reset password - // flow, and once reset password flow completes, invoke - // `signIn` api to trigger signIn flow again. - Log.i("AuthQuickstart", "Password reset required" + error); - } else { - Log.e("AuthQuickstart", "SignIn failed: " + error); - } } + }, + error -> { + if (error instanceof UserNotConfirmedException) { + // User was not confirmed during the signup process. + // Invoke `confirmSignUp` api to confirm the user if + // they have the confirmation code. If they do not have the + // confirmation code, invoke `resendSignUpCode` to send the + // code again. + // After the user is confirmed, invoke the `signIn` api again. + Log.i("AuthQuickstart", "Signup confirmation required" + error); + } else if (error instanceof PasswordResetRequiredException) { + // User needs to reset their password. + // Invoke `resetPassword` api to start the reset password + // flow, and once reset password flow completes, invoke + // `signIn` api to trigger signIn flow again. + Log.i("AuthQuickstart", "Password reset required" + error); + } else { + Log.e("AuthQuickstart", "SignIn failed: " + error); + } + } ); ``` @@ -1465,7 +1504,11 @@ RxAmplify.Auth.signIn("username", "password", options).subscribe( If the next step is `CONFIRM_SIGN_IN_WITH_SMS_MFA_CODE`, Amplify Auth has sent the user a random code over SMS, and is waiting to find out if the user successfully received it. To handle this step, your app's UI must prompt the user to enter the code. After the user enters the code, your implementation must pass the value to Amplify Auth `confirmSignIn` API. -Note: the signIn result also includes an `AuthCodeDeliveryDetails` member. It includes additional information about the code delivery such as the partial phone number of the SMS recipient. + + +**Note:** The result also includes an `AuthCodeDeliveryDetails` member. It includes additional information about the code delivery such as the partial phone number of the SMS recipient. + + @@ -1572,7 +1615,21 @@ After the user enters the code, your implementation must pass the value to Ampli If the next step is `CONFIRM_SIGN_IN_WITH_EMAIL_MFA_CODE`, Amplify Auth has sent the user a random code to their email address and is waiting to find out if the user successfully received it. To handle this step, your app's UI must prompt the user to enter the code. After the user enters the code, your implementation must pass the value to Amplify Auth `confirmSignIn` API. -Note: the signIn result also includes an `AuthCodeDeliveryDetails` member. It includes additional information about the code delivery such as the partial email address of the recipient. + + +**Note:** The result also includes an `AuthCodeDeliveryDetails` member. It includes additional information about the code delivery such as the partial email address of the recipient. + + + +## Confirm sign-in with OTP + +If the next step is `CONFIRM_SIGN_IN_WITH_OTP`, Amplify Auth has sent the user a random code to the medium of the user's choosing (e.g. SMS or email) and is waiting for the user to verify that code. To handle this step, your app's UI must prompt the user to enter the code. After the user enters the code, pass the value to the `confirmSignIn` API. + + + +**Note:** The result includes an `AuthCodeDeliveryDetails` member. It includes additional information about the code delivery, such as the partial email address of the recipient, which can be used to prompt the user on where to look for the code. + + ## Continue sign-in with MFA Selection @@ -1592,6 +1649,12 @@ Once the authenticator app is set up, the user can generate a TOTP code and prov If the next step is `CONTINUE_SIGN_IN_WITH_MFA_SETUP_SELECTION`, the user must select the MFA method to setup. Amplify Auth currently supports SMS, TOTP, and email as MFA methods. After the user selects an MFA method, your implementation must pass the selected MFA method to Amplify Auth using `confirmSignIn` API. +## Continue sign-in with First Factor Selection + +If the next step is `CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION`, the user must select an authentication factor to use either because they did not specify one or because the one they chose is not supported (e.g. selecting SMS when they don't have a phone number registered to their account). Amplify Auth currently supports SMS, email, password, and webauthn as authentication factors. After the user selects an authentication method, your implementation must pass the selected authentication method to Amplify Auth using `confirmSignIn` API. + +Visit the [sign-in documentation](/[platform]/build-a-backend/auth/connect-your-frontend/sign-in/#sign-in-with-passwordless-methods) to see examples on how to call the `confirmSignIn` API. + ## Confirm sign-in with custom challenge If the next step is `CONFIRM_SIGN_IN_WITH_CUSTOM_CHALLENGE`, Amplify Auth is awaiting completion of a custom authentication challenge. The challenge is based on the Lambda trigger you setup when you configured a [custom sign in flow](/[platform]/build-a-backend/auth/customize-auth-lifecycle/custom-auth-flows/#sign-in-a-user). To complete this step, you should prompt the user for the custom challenge answer, and pass the answer to the `confirmSignIn` API. @@ -1933,7 +1996,10 @@ RxAmplify.Auth.confirmSignUp( This call fetches the current logged in user and should be used after a user has been successfully signed in. If the user is signed in, it will return the current userId and username. -Note: An empty string will be assigned to userId and/or username, if the values are not present in the accessToken. + + +**Note:** An empty string will be assigned to userId and/or username, if the values are not present in the accessToken. + 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 15272c691cd..59c955c5fee 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 @@ -209,7 +209,7 @@ RxAmplify.Auth.signIn("username", "password") func signIn(username: String, password: String) async { do { let signInResult = try await Amplify.Auth.signIn( - username: username, + username: username, password: password ) if signInResult.isSignedIn { @@ -230,7 +230,7 @@ func signIn(username: String, password: String) async { func signIn(username: String, password: String) -> AnyCancellable { Amplify.Publisher.create { try await Amplify.Auth.signIn( - username: username, + username: username, password: password ) }.sink { @@ -280,10 +280,12 @@ The `signIn` API response will include a `nextStep` property, which can be used | `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_MFA_CODE` | The sign-in must be confirmed with a SMS code from the user. Complete the process with `confirmSignIn`. | | `CONFIRM_SIGN_IN_WITH_OTP` | The sign-in must be confirmed with a code from the user (sent via SMS or Email). Complete the process with `confirmSignIn`. | -| `CONTINUE_SIGN_IN_WITH_MFA_SELECTION` | The user must select their mode of MFA verification before signing in. 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_MFA_SETUP_SELECTION` | 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`. | +| `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_TOTP_SETUP` | The TOTP setup process must be continued. Complete the process with `confirmSignIn`. | | `CONTINUE_SIGN_IN_WITH_EMAIL_MFA_SETUP` | The EMAIL setup process must be continued. Complete the process by passing a valid email address to `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`. | | `RESET_PASSWORD` | The user must reset their password via `resetPassword`. | | `CONFIRM_SIGN_UP` | The user hasn't completed the sign-up flow fully and must be confirmed via `confirmSignUp`. | | `DONE` | The sign in process has been completed. | @@ -608,6 +610,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_MFA_CODE` | The sign-in must be confirmed with a SMS code from the user. Complete the process with `confirmSignIn`. | | `CONFIRM_SIGN_IN_WITH_OTP` | The sign-in must be confirmed with a code from the user (sent via SMS or Email). 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 `MFAType.EMAIL.challengeResponse` or `MFAType.TOTP.challengeResponse` to `confirmSignIn`. | | `CONTINUE_SIGN_IN_WITH_TOTP_SETUP` | The TOTP setup process must be continued. Complete the process with `confirmSignIn`. | @@ -735,7 +739,7 @@ Amplify.Auth.confirmSignIn("code received via SMS", ```kotlin try { val result = Amplify.Auth.confirmSignIn("code received via SMS") - Log.i("AuthQuickstart", "Confirmed signin: $result") + Log.i("AuthQuickstart", "Confirmed signin: $result") } catch (error: AuthException) { Log.e("AuthQuickstart", "Failed to confirm signin", error) } @@ -799,10 +803,10 @@ func confirmSignIn() -> AnyCancellable { ## Sign in with an external identity provider -To sign in using an external identity provider such as Google, use the `signInWithRedirect` function. - +To sign in using an external identity provider such as Google, use the `signInWithRedirect` function. + ```ts import { signInWithRedirect } from "aws-amplify/auth" @@ -840,7 +844,9 @@ await autoSignIn(); ``` - + + ### Install native module @@ -879,6 +885,8 @@ Add the `intent-filter` to your application's main activity, replacing `myapp` w +To sign in using an external identity provider such as Google, use the `signInWithWebUI` function. + ### How It Works Sign-in with web UI will display the sign-in UI inside a webview. After the sign-in process is complete, the sign-in UI will redirect back to your app. @@ -945,7 +953,10 @@ Future socialSignIn() async { ``` -## Update AndroidManifest.xml + +To sign in using an external identity provider such as Google, use the `signInWithSocialWebUI` function. + +### Update AndroidManifest.xml Add the following activity and queries tag to your app's `AndroidManifest.xml` file, replacing `myapp` with your redirect URI prefix if necessary: @@ -967,7 +978,7 @@ your redirect URI prefix if necessary: ``` -## Launch Social Web UI Sign In +### Launch Social Web UI Sign In Sweet! You're now ready to launch sign in with your social provider's web UI. @@ -1030,9 +1041,12 @@ RxAmplify.Auth.signInWithSocialWebUI(AuthProvider.facebook(), this) -## Update Info.plist -Sign-in with web UI requires the Amplify plugin to show up the sign-in UI inside a webview. After the sign-in process is complete it will redirect back to your app. +To sign in using an external identity provider such as Google, use the `signInWithWebUI` function. + +### Update Info.plist + +Sign-in with web UI requires the Amplify plugin to show up the sign-in UI inside a webview. After the sign-in process is complete it will redirect back to your app. You have to enable this in your app's `Info.plist`. Right click Info.plist and then choose Open As > Source Code. Add the following entry in the URL scheme: ```xml @@ -1060,7 +1074,7 @@ You have to enable this in your app's `Info.plist`. Right click Info.plist and t When creating a new SwiftUI app using Xcode 13 no longer require configuration files such as the Info.plist. If you are missing this file, click on the project target, under Info, Url Types, and click '+' to add a new URL Type. Add `myapp` to the URL Schemes. You should see the Info.plist file now with the entry for CFBundleURLSchemes. -## Launch Social Web UI Sign In +### Launch Social Web UI Sign In Invoke the following API with the provider you're using (shown with Facebook below): @@ -1107,12 +1121,12 @@ 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, visit the [concepts page for passwordless](/[platform]/build-a-backend/auth/concepts/passwordless/). ### SMS OTP @@ -1147,7 +1161,108 @@ 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. + +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(), + 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 +// First confirm the challenge type +Amplify.Auth.confirmSignIn( + AuthFactorType.SMS_OTP.challengeResponse, + { result -> + if (result.nextStep.signInStep == AuthSignInStep.CONFIRM_SIGN_IN_WITH_OTP) { + // Show UI to collect OTP + } + }, + { error -> + Log.e("AuthQuickstart", "Failed to sign in", error) + } +) + +// 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) + } +) +``` + + + + +```kotlin +// First confirm the challenge type +var result = Amplify.Auth.confirmSignIn(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 +result = Amplify.Auth.confirmSignIn("123456") + +// result.nextStep.signInStep should be "DONE" now +``` + + + + +```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()) + ); + +// 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()) + ); +``` + + + + @@ -1260,7 +1375,109 @@ 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. + + + + +```java +// First confirm the challenge type +Amplify.Auth.confirmSignIn( + AuthFactorType.EMAIL_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 +// First confirm the challenge type +Amplify.Auth.confirmSignIn( + AuthFactorType.EMAIL_OTP.challengeResponse, + { result -> + if (result.nextStep.signInStep == AuthSignInStep.CONFIRM_SIGN_IN_WITH_OTP) { + // Show UI to collect OTP + } + }, + { error -> + Log.e("AuthQuickstart", "Failed to sign in", error) + } +) + +// 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) + } +) +``` + + + + +```kotlin +// First confirm the challenge type +var result = Amplify.Auth.confirmSignIn(AuthFactorType.EMAIL_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 +result = Amplify.Auth.confirmSignIn("123456") + +// result.nextStep.signInStep should be "DONE" now +``` + + + + +```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()) + ); + +// 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()) + ); +``` + + + + + @@ -1340,7 +1557,7 @@ func confirmSignIn() -> AnyCancellable { - + ### WebAuthn Passkeys {/* blurb with supplemental information about handling sign-in, events, etc. */} @@ -1366,7 +1583,97 @@ 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. + + +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. + + + + + +```java +// Pass the calling activity +AuthSignInOptions options = AWSCognitoAuthConfirmSignInOptions.builder() + .callingActivity(activity) + .build(); + +// Confirm WebAuthn as the challenge type +Amplify.Auth.confirmSignIn( + AuthFactorType.WEB_AUTHN.getChallengeResponse(), + options, + result -> Log.i("AuthQuickStart", "Next sign in step: " + result.getNextStep()), + error -> Log.e("AuthQuickstart", "Failed to sign in", error) +); +``` + + + + +```kotlin +// Pass the calling activity +val options = AWSCognitoAuthConfirmSignInOptions.builder() + .callingActivity(activity) + .build() + +// Confirm WebAuthn as the challenge type +Amplify.Auth.confirmSignIn( + AuthFactorType.WEB_AUTHN.name, + options, + { result -> Log.i("AuthQuickStart", "Next sign in step: ${result.nextStep}") }, + { error -> Log.e("AuthQuickstart", "Failed to sign in", error) } +) +``` + + + + +```kotlin +// Pass the calling activity +val options = AWSCognitoAuthConfirmSignInOptions.builder() + .callingActivity(activity) + .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) +} +``` + + + + +```java +// Pass the calling activity +AuthSignInOptions options = AWSCognitoAuthConfirmSignInOptions.builder() + .callingActivity(activity) + .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) + ); +``` + + + + +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`. +- `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. +- `WebAuthnFailedException` - This exception is used for other errors that may occur with WebAuthn. Inspect the `cause` to determine the best course of action. @@ -1380,12 +1687,15 @@ if (signInNextStep.signInStep === 'DONE') { - + ### 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', @@ -1400,14 +1710,119 @@ if (confirmSignInNextStep.signInStep === 'DONE') { console.log('Sign in successful!'); } ``` + + + + + + + +```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()) +); + +// Then pass that password into the confirmSignIn API +Amplify.Auth.confirmSignIn( + "password", + result -> { + // result.getNextStep().getSignInStep() should be "DONE" now + }, + error -> Log.e("AuthQuickstart", error.toString()) +); +``` + + + + +```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) + } +) + +// 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) + } +) +``` + + + + +```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 +} + +// Then pass that password into the confirmSignIn API +result = Amplify.Auth.confirmSignIn("password") + +// result.nextStep.signInStep should be "DONE" now +``` + + + + +```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()) + ); + +// 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()) + ); +``` + + + + + ### First Factor Selection -Omit the `preferredChallenge` parameter to discover what first factors are available for a given user. +Omit the `preferredChallenge` parameter to discover what first factors are available for a given user. 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', @@ -1429,7 +1844,119 @@ if ( } ``` + + + + + + +```java +// Retrieve the authentication factors by calling .availableFactors +AWSCognitoAuthSignInOptions options = + AWSCognitoAuthSignInOptions + .builder() + .authFlowType(AuthFlowType.USER_AUTH) + .callingActivity(callingActivity) + .build(); +Amplify.Auth.signIn( + "hello@example.com", + null, + options, + 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()) +); +``` + + + + +```kotlin +// Retrieve the authentication factors by calling .availableFactors +val options = AWSCognitoAuthSignInOptions.builder() + .authFlowType(AuthFlowType.USER_AUTH) + .callingActivity(callingActivity) + .build() +Amplify.Auth.signIn( + "hello@example.com", + null, + options, + { result -> + if (result.nextStep.signInStep == AuthSignInStep.CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION) { + Log.i( + "AuthQuickstart", + "Available factors for this user: ${result.nextStep.availableFactors}" + ) + } + }, + { error -> + Log.e("AuthQuickstart", "Failed to sign in", error) + } +) +``` + + + + +```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 + ) + 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) +} +``` + + + + +```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) + .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()) + ); +``` + + + + + 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 436ce634a0e..f06dcbdb8bc 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 @@ -528,11 +528,11 @@ 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/) +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 @@ -578,7 +578,168 @@ if (confirmSignUpNextStep.signUpStep === 'DONE') { -{/* */} + + + +```java +// Sign up using a phone number +ArrayList attributes = new ArrayList<>(); +attributes.add(new AuthUserAttribute(AuthUserAttributeKey.phoneNumber(), "+15551234567")); + +Amplify.Auth.signUp( + "hello@example.com", + null, + AuthSignUpOptions.builder().userAttributes(attributes).build(), + result -> { + if (result.isSignUpComplete()) { + Log.i("AuthQuickstart", "Sign up is complete"); + } else if (result.getNextStep().getSignUpStep() == AuthSignUpStep.CONFIRM_SIGN_UP_STEP) { + Log.i("AuthQuickstart", "Code Deliver Medium: " + + result.getNextStep().getCodeDeliveryDetails().getDeliveryMedium()); + Log.i("AuthQuickstart", "Code Deliver Destination: " + + result.getNextStep().getCodeDeliveryDetails().getDestination()); + } + }, + error -> Log.e("AuthQuickstart", error.toString()) +); + +// Confirm sign up with the OTP received +Amplify.Auth.confirmSignUp( + "hello@example.com", + "123456", + result -> { + if (result.isSignUpComplete()) { + Log.i("AuthQuickstart", "Sign up is complete"); + } + }, + error -> Log.e("AuthQuickstart", error.toString()) +); +``` + + + + +```kotlin +// Sign up using a phone number +val attributes = listOf( + AuthUserAttribute(AuthUserAttributeKey.phoneNumber(), "+15555551234") +) +val options = + AuthSignUpOptions + .builder() + .userAttributes(attributes) + .build() + +Amplify.Auth.signUp( + "hello@example.com", + null, + options, + { result -> + if (result.isSignUpComplete) { + Log.i("AuthQuickstart", "Sign up is complete") + } else if (result.nextStep.signUpStep == AuthSignUpStep.CONFIRM_SIGN_UP_STEP) { + Log.i("AuthQuickstart", "Code Deliver Medium: " + + "${result.nextStep.codeDeliveryDetails?.deliveryMedium}") + Log.i("AuthQuickstart", "Code Deliver Destination: " + + "${result.nextStep.codeDeliveryDetails?.destination}") + } + }, + { Log.e("AuthQuickstart", "Failed to sign up", it) } +) + +// Confirm sign up with the OTP received +Amplify.Auth.confirmSignUp( + "hello@example.com", + "123456", + { result -> + if (result.nextStep.signUpStep == AuthSignUpStep.DONE) { + Log.i("AuthQuickstart", "Sign up is complete") + } + }, + { Log.e("AuthQuickstart", "Failed to sign up", it) } +) +``` + + + + +```kotlin +// Sign up using a phone number +val attributes = listOf( + AuthUserAttribute(AuthUserAttributeKey.phoneNumber(), "+15555551234") +) +val options = + AuthSignUpOptions + .builder() + .userAttributes(attributes) + .build() +var result = Amplify.Auth.signUp("hello@example.com", null, options) + +if (result.isSignUpComplete) { + Log.i("AuthQuickstart", "Sign up is complete") +} else if (result.nextStep.signUpStep == AuthSignUpStep.CONFIRM_SIGN_UP_STEP) { + Log.i("AuthQuickstart", "Code Deliver Medium: " + + "${result.nextStep.codeDeliveryDetails?.deliveryMedium}") + Log.i("AuthQuickstart", "Code Deliver Destination: " + + "${result.nextStep.codeDeliveryDetails?.destination}") +} + +// Confirm sign up with the OTP received +result = Amplify.Auth.confirmSignUp( + "hello@example.com", + "123456" +) + +if (result.nextStep.signUpStep == AuthSignUpStep.DONE) { + Log.i("AuthQuickstart", "Sign up is complete") +} +``` + + + + +```java +// Sign up using a phone number +ArrayList attributes = new ArrayList<>(); +attributes.add(new AuthUserAttribute(AuthUserAttributeKey.phoneNumber(), "+15551234567")); + +RxAmplify.Auth.signUp( + "hello@example.com", + null, + AuthSignUpOptions.builder().userAttributes(attributes).build() +) + .subscribe( + result -> { + if (result.isSignUpComplete()) { + Log.i("AuthQuickstart", "Sign up is complete"); + } else if (result.getNextStep().getSignUpStep() == AuthSignUpStep.CONFIRM_SIGN_UP_STEP) { + Log.i("AuthQuickstart", "Code Deliver Medium: " + + result.getNextStep().getCodeDeliveryDetails().getDeliveryMedium()); + Log.i("AuthQuickstart", "Code Deliver Destination: " + + result.getNextStep().getCodeDeliveryDetails().getDestination()); + } + }, + error -> Log.e("AuthQuickstart", error.toString()) + ); + +// Confirm sign up with the OTP received +RxAmplify.Auth.confirmSignUp( + "hello@example.com", + "123456" +) + .subscribe( + result -> { + if (result.isSignUpComplete()) { + Log.i("AuthQuickstart", "Sign up is complete"); + } + }, + error -> Log.e("AuthQuickstart", error.toString()) + ); +``` + + + + @@ -726,7 +887,167 @@ if (confirmSignUpNextStep.signUpStep === 'DONE') { -{/* */} + + + +```java +// Sign up using an email address +ArrayList attributes = new ArrayList<>(); +attributes.add(new AuthUserAttribute(AuthUserAttributeKey.email(), "hello@example.com")); + +Amplify.Auth.signUp( + "hello@example.com", + null, + AuthSignUpOptions.builder().userAttributes(attributes).build(), + result -> { + if (result.isSignUpComplete()) { + Log.i("AuthQuickstart", "Sign up is complete"); + } else if (result.getNextStep().getSignUpStep() == AuthSignUpStep.CONFIRM_SIGN_UP_STEP) { + Log.i("AuthQuickstart", "Code Deliver Medium: " + + result.getNextStep().getCodeDeliveryDetails().getDeliveryMedium()); + Log.i("AuthQuickstart", "Code Deliver Destination: " + + result.getNextStep().getCodeDeliveryDetails().getDestination()); + } + }, + error -> Log.e("AuthQuickstart", error.toString()) +); + +// Confirm sign up with the OTP received +Amplify.Auth.confirmSignUp( + "hello@example.com", + "123456", + result -> { + if (result.isSignUpComplete()) { + Log.i("AuthQuickstart", "Sign up is complete"); + } + }, + error -> Log.e("AuthQuickstart", error.toString()) +); +``` + + + + +```kotlin +// Sign up using an email address +val attributes = listOf( + AuthUserAttribute(AuthUserAttributeKey.email(), "my@email.com") +) +val options = + AuthSignUpOptions + .builder() + .userAttributes(attributes) + .build() + +Amplify.Auth.signUp( + "hello@example.com", + null, + options, + { result -> + if (result.isSignUpComplete) { + Log.i("AuthQuickstart", "Sign up is complete") + } else if (result.nextStep.signUpStep == AuthSignUpStep.CONFIRM_SIGN_UP_STEP) { + Log.i("AuthQuickstart", "Code Deliver Medium: " + + "${result.nextStep.codeDeliveryDetails?.deliveryMedium}") + Log.i("AuthQuickstart", "Code Deliver Destination: " + + "${result.nextStep.codeDeliveryDetails?.destination}") + } + }, + { Log.e("AuthQuickstart", "Failed to sign up", it) } +) + +// Confirm sign up with the OTP received +Amplify.Auth.confirmSignUp( + "hello@example.com", + "123456", + { result -> + if (result.nextStep.signUpStep == AuthSignUpStep.DONE) { + Log.i("AuthQuickstart", "Sign up is complete") + } + }, + { Log.e("AuthQuickstart", "Failed to sign up", it) } +) +``` + + + + +```kotlin +// Sign up using an email address +val attributes = listOf( + AuthUserAttribute(AuthUserAttributeKey.email(), "my@email.com") +) +val options = + AuthSignUpOptions + .builder() + .userAttributes(attributes) + .build() +var result = Amplify.Auth.signUp("hello@example.com", null, options) + +if (result.isSignUpComplete) { + Log.i("AuthQuickstart", "Sign up is complete") +} else if (result.nextStep.signUpStep == AuthSignUpStep.CONFIRM_SIGN_UP_STEP) { + Log.i("AuthQuickstart", "Code Deliver Medium: " + + "${result.nextStep.codeDeliveryDetails?.deliveryMedium}") + Log.i("AuthQuickstart", "Code Deliver Destination: " + + "${result.nextStep.codeDeliveryDetails?.destination}") +} + +// Confirm sign up with the OTP received +result = Amplify.Auth.confirmSignUp( + "hello@example.com", + "123456" +) + +if (result.nextStep.signUpStep == AuthSignUpStep.DONE) { + Log.i("AuthQuickstart", "Sign up is complete") +} +``` + + + + +```java +// Sign up using an email address +ArrayList attributes = new ArrayList<>(); +attributes.add(new AuthUserAttribute(AuthUserAttributeKey.email(), "my@email.com")); + +RxAmplify.Auth.signUp( + "hello@example.com", + null, + AuthSignUpOptions.builder().userAttributes(attributes).build() +) + .subscribe( + result -> { + if (result.isSignUpComplete()) { + Log.i("AuthQuickstart", "Sign up is complete"); + } else if (result.getNextStep().getSignUpStep() == AuthSignUpStep.CONFIRM_SIGN_UP_STEP) { + Log.i("AuthQuickstart", "Code Deliver Medium: " + + result.getNextStep().getCodeDeliveryDetails().getDeliveryMedium()); + Log.i("AuthQuickstart", "Code Deliver Destination: " + + result.getNextStep().getCodeDeliveryDetails().getDestination()); + } + }, + error -> Log.e("AuthQuickstart", error.toString()) + ); + +// Confirm sign up with the OTP received +RxAmplify.Auth.confirmSignUp( + "hello@example.com", + "123456" +) + .subscribe( + result -> { + if (result.isSignUpComplete()) { + Log.i("AuthQuickstart", "Sign up is complete"); + } + }, + error -> Log.e("AuthQuickstart", error.toString()) + ); +``` + + + @@ -854,7 +1175,120 @@ if (confirmSignUpNextStep.signUpStep === 'COMPLETE_AUTO_SIGN_IN') { -{/* */} + + + +```java +private void confirmSignUp(String username, String confirmationCode) { + // Confirm sign up with the OTP received then auto sign in + Amplify.Auth.confirmSignUp( + username, + confirmationCode, + result -> { + if (result.getNextStep().getSignUpStep() == AuthSignUpStep.COMPLETE_AUTO_SIGN_IN) { + Log.i("AuthQuickstart", "Sign up is complete, auto sign in"); + autoSignIn(); + } + }, + error -> Log.e("AuthQuickstart", error.toString()) + ); +} + +private void autoSignIn() { + Amplify.Auth.autoSignIn( + result -> Log.i("AuthQuickstart", "Sign in is complete"), + error -> Log.e("AuthQuickstart", error.toString()) + ); +} +``` + + + + +```kotlin +fun confirmSignUp(username: String, confirmationCode: String) { + // Confirm sign up with the OTP received + Amplify.Auth.confirmSignUp( + username, + confirmationCode, + { signUpResult -> + if (signUpResult.nextStep.signUpStep == AuthSignUpStep.COMPLETE_AUTO_SIGN_IN) { + Log.i("AuthQuickstart", "Sign up is complete, auto sign in") + autoSignIn() + } + }, + { Log.e("AuthQuickstart", "Failed to sign up", it) } + ) +} +fun autoSignIn() { + Amplify.Auth.autoSignIn( + { signInResult -> + Log.i("AuthQuickstart", "Sign in is complete") + }, + { Log.e("AuthQuickstart", "Failed to sign in", it) } + ) +} +``` + + + + +```kotlin +suspend fun confirmSignUp(username: String, confirmationCode: String) { + // Confirm sign up with the OTP received then auto sign in + val result = Amplify.Auth.confirmSignUp( + "hello@example.com", + "123456" + ) + + if (result.nextStep.signUpStep == AuthSignUpStep.COMPLETE_AUTO_SIGN_IN) { + Log.i("AuthQuickstart", "Sign up is complete, auto sign in") + autoSignIn() + } +} + +suspend fun autoSignIn() { + val result = Amplify.Auth.autoSignIn() + if (result.isSignedIn) { + Log.i("AuthQuickstart", "Sign in is complete") + } else { + Log.e("AuthQuickstart", "Sign in did not complete $result") + } +} +``` + + + + +```java +private void confirmSignUp(String username, String confirmationCode) { + // Confirm sign up with the OTP received then auto sign in + RxAmplify.Auth.confirmSignUp( + username, + confirmationCode + ) + .subscribe( + result -> { + if (result.getNextStep().getSignUpStep() == AuthSignUpStep.COMPLETE_AUTO_SIGN_IN) { + Log.i("AuthQuickstart", "Sign up is complete, auto sign in"); + autoSignIn(); + } + }, + error -> Log.e("AuthQuickstart", error.toString()) + ); +} + +private void autoSignIn() { + RxAmplify.Auth.autoSignIn() + .subscribe( + result -> Log.i("AuthQuickstart", "Sign in is complete" + result.toString()), + error -> Log.e("AuthQuickstart", error.toString()) + ); +} +``` + + + 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 5c82f2da750..f0bcbe79167 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 @@ -10,7 +10,8 @@ export const meta = { 'react', 'react-native', 'swift', - 'vue' + 'vue', + 'android' ] }; @@ -28,7 +29,7 @@ export function getStaticProps() { -`AWSCognitoAuthPlugin` allows you to switch between different auth flows while initiating signIn. You can configure the flow in the `amplifyconfiguration.json` file or pass the `authFlowType` as a runtime parameter to the `signIn` api call. +`AWSCognitoAuthPlugin` allows you to switch between different auth flows while initiating signIn. You can configure the flow in the `amplify_outputs.json` file or pass the `authFlowType` as a runtime parameter to the `signIn` api call. For client side authentication there are four different flows that can be configured during runtime: @@ -101,30 +102,6 @@ func signIn(username: String, password: String) async throws { } ``` -### Set up auth backend - -In order to use the authentication flow `USER_PASSWORD_AUTH`, your Cognito app client has to be configured to allow it. Amplify Gen 2 enables SRP auth by default. To enable USER_PASSWORD_AUTH, you can update the `backend.ts` file with the following changes: - -```ts title="amplify/backend.ts" -import { defineBackend } from '@aws-amplify/backend' -import { auth } from './auth/resource' -import { data } from './data/resource' - -const backend = defineBackend({ - auth, - data, -}); - -// highlight-start -backend.auth.resources.cfnResources.cfnUserPoolClient.explicitAuthFlows = [ - "ALLOW_USER_PASSWORD_AUTH", - "ALLOW_USER_SRP_AUTH", - "ALLOW_USER_AUTH", - "ALLOW_REFRESH_TOKEN_AUTH" -]; -// highlight-end -``` - ### Migrate users with Amazon Cognito Amazon Cognito provides a trigger to migrate users from your existing user directory seamlessly into Cognito. You achieve this by configuring your User Pool's "Migration" trigger which invokes a Lambda function whenever a user that does not already exist in the user pool authenticates, or resets their password. @@ -274,6 +251,387 @@ To create a CAPTCHA challenge with a Lambda Trigger, please visit [AWS Amplify G + + +`AWSCognitoAuthPlugin` allows you to switch between different auth flows while initiating signIn. You can configure the flow in the `amplify_outputs.json` file or pass the `authFlowType` as a option to the `signIn` api call. + +For client side authentication there are four different flows that can be configured during runtime: + +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. + +2. `USER_PASSWORD_AUTH`: The `USER_PASSWORD_AUTH` flow will send user credentials unencrypted to the back-end. If you want to migrate users to Cognito using the "Migration" trigger and avoid forcing users to reset their passwords, you will need to use this authentication type because the Lambda function invoked by the trigger needs to verify the supplied credentials. + +3. `CUSTOM_AUTH_WITH_SRP`: The `CUSTOM_AUTH_WITH_SRP` flow is used to start with SRP authentication and then switch to custom authentication. This is useful if you want to use SRP for the initial authentication and then use custom authentication for subsequent authentication attempts. + +4. `CUSTOM_AUTH_WITHOUT_SRP`: The `CUSTOM_AUTH_WITHOUT_SRP` 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. `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`. + +`Auth` can be configured to use the different flows at runtime by calling `signIn` with `AWSCognitoAuthSignInOptions`'s `authFlowType` as `AuthFlowType.USER_PASSWORD_AUTH`, `AuthFlowType.CUSTOM_AUTH_WITHOUT_SRP`, `AuthFlowType.CUSTOM_AUTH_WITH_SRP`, or `AuthFlowType.USER_AUTH`. If you do not specify the `AuthFlowType` in `AWSCognitoAuthSignInOptions`, the default flow specified in `amplify_outputs.json` will be used. + + + +Runtime configuration will take precedence and will override any auth flow type configuration present in `amplify_outputs.json`. + + + +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 `EMAIL_OTP`, `SMS_OTP`, `WEB_AUTHN`, `PASSWORD` or `PASSWORD_SRP`. + + +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. + + +If the desired first factor is known before the sign in flow is initiated it can be passed to the initial sign in call. + + + + +```java +// PASSWORD_SRP / PASSWORD +// Sign in with preferred challenge as password +// NOTE: Password must be provided in the same step +AuthSignInOptions options = AWSCognitoAuthSignInOptions.builder() + .callingActivity(activity) + .authFlowType(AuthFlowType.USER_AUTH) + .preferredFirstFactor(AuthFactorType.PASSWORD_SRP) // or "PASSWORD" + .build(); +Amplify.Auth.signIn( + username, + password, + options, + result -> Log.i("AuthQuickStart", "Next step for sign in is " + result.getNextStep()), + error -> Log.e("AuthQuickStart", "Failed to confirm sign in", error) +); + +// WEB_AUTHN / EMAIL_OTP / SMS_OTP +// Sign in with preferred passwordless challenge +// No user input is required at this step +AuthSignInOptions options = AWSCognitoAuthSignInOptions.builder() + .callingActivity(activity) + .authFlowType(AuthFlowType.USER_AUTH) + .preferredFirstFactor(AuthFactorType.WEB_AUTHN) // or "EMAIL_OTP" or "SMS_OTP" + .build(); +Amplify.Auth.signIn( + username, + null, + options, + result -> Log.i("AuthQuickStart", "Next step for sign in is " + result.getNextStep()), + error -> Log.e("AuthQuickStart", "Failed to confirm sign in", error) +); +``` + + + + +```kotlin +// PASSWORD_SRP / PASSWORD +// Sign in with preferred challenge as password +// NOTE: Password must be provided in the same step +val options = AWSCognitoAuthSignInOptions.builder() + .callingActivity(activity) + .authFlowType(AuthFlowType.USER_AUTH) + .preferredFirstFactor(AuthFactorType.PASSWORD_SRP) // or "PASSWORD" + .build() +Amplify.Auth.signIn( + username, + password, + options, + { result -> Log.i("AuthQuickStart", "Next step for sign in is ${result.nextStep}") }, + { error -> Log.e("AuthQuickStart", "Failed to confirm sign in", error) } +) + +// WEB_AUTHN / EMAIL_OTP / SMS_OTP +// Sign in with preferred passwordless challenge +// No user input is required at this step +val options = AWSCognitoAuthSignInOptions.builder() + .callingActivity(activity) + .authFlowType(AuthFlowType.USER_AUTH) + .preferredFirstFactor(AuthFactorType.WEB_AUTHN) // or "EMAIL_OTP" or "SMS_OTP" + .build() +Amplify.Auth.signIn( + username, + null, + options, + { result -> Log.i("AuthQuickStart", "Next step for sign in is ${result.nextStep}") }, + { error -> Log.e("AuthQuickStart", "Failed to confirm sign in", error) } +) +``` + + + + +```kotlin +// PASSWORD_SRP / PASSWORD +// Sign in with preferred challenge as password +// NOTE: Password must be provided in the same step +try { + val options = AWSCognitoAuthSignInOptions.builder() + .callingActivity(activity) + .authFlowType(AuthFlowType.USER_AUTH) + .preferredFirstFactor(AuthFactorType.PASSWORD_SRP) // or "PASSWORD" + .build() + val result = Amplify.Auth.signIn( + username = "hello@example.com", + password = "password", + options = options + ) + Log.i("AuthQuickstart", "Next step for sign in is ${result.nextStep}") +} catch (error: AuthException) { + Log.e("AuthQuickstart", "Sign in failed", error) +} + +// WEB_AUTHN / EMAIL_OTP / SMS_OTP +// Sign in with preferred passwordless challenge +// No user input is required at this step +try { + val options = AWSCognitoAuthSignInOptions.builder() + .callingActivity(activity) + .authFlowType(AuthFlowType.USER_AUTH) + .preferredFirstFactor(AuthFactorType.WEB_AUTHN) // or "EMAIL_OTP" or "SMS_OTP" + .build() + val result = Amplify.Auth.signIn( + username = "hello@example.com", + password = null, + options = options + ) + Log.i("AuthQuickstart", "Next step for sign in is ${result.nextStep}") +} catch (error: AuthException) { + Log.e("AuthQuickstart", "Sign in failed", error) +} +``` + + + + +```java +// PASSWORD_SRP / PASSWORD +// Sign in with preferred challenge as password +// NOTE: Password must be provided in the same step +AuthSignInOptions options = AWSCognitoAuthSignInOptions.builder() + .callingActivity(activity) + .authFlowType(AuthFlowType.USER_AUTH) + .preferredFirstFactor(AuthFactorType.PASSWORD_SRP) // or "PASSWORD" + .build(); +RxAmplify.Auth.signIn(username, password, options) + .subscribe( + result -> Log.i("AuthQuickstart", "Next step for sign in is " + result.getNextStep()), + error -> Log.e("AuthQuickstart", "Failed to confirm sign in", error)) + ); + +// WEB_AUTHN / EMAIL_OTP / SMS_OTP +// Sign in with preferred passwordless challenge +// No user input is required at this step +AuthSignInOptions options = AWSCognitoAuthSignInOptions.builder() + .callingActivity(activity) + .authFlowType(AuthFlowType.USER_AUTH) + .preferredFirstFactor(AuthFactorType.WEB_AUTHN) // or "EMAIL_OTP" or "SMS_OTP" + .build(); +RxAmplify.Auth.signIn(username, null, options) + .subscribe( + result -> Log.i("AuthQuickstart", "Next step for sign in is " + result.getNextStep()), + error -> Log.e("AuthQuickstart", "Failed to confirm sign in", error)) + ); +``` + + + + +If the preferred first factor is not supplied or is unavailable, and the user has multiple factors available, the flow will continue to select an available first factor by returning an `AuthNextSignInStep.signInStep` value of `CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION`, and a list of `AuthNextSignInStep.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 `WEB_AUTHN` factor selection: + + + + +```java +AuthConfirmSignInOptions options = AWSCognitoAuthConfirmSignInOptions.builder() + .callingActivity(activity) + .build(); +Amplify.Auth.confirmSignIn( + AuthFactorType.WEB_AUTHN.getChallengeResponse(), + options, + result -> Log.i("AuthQuickStart", "Next step for sign in is " + result.getNextStep()), + error -> Log.e("AuthQuickStart", "Failed to confirm sign in", error) +); +``` + + + + +```kotlin +val options = AWSCognitoAuthConfirmSignInOptions.builder() + .callingActivity(activity) + .build() +Amplify.Auth.confirmSignIn( + AuthFactorType.WEB_AUTHN.challengeResponse, + options, + { result -> Log.i("AuthQuickStart", "Next step for sign in is ${result.nextStep}") }, + { error -> Log.e("AuthQuickStart", "Failed to confirm sign in", error) } +) +``` + + + + +```kotlin +try { + val options = AWSCognitoAuthConfirmSignInOptions.builder() + .callingActivity(activity) + .build() + val result = Amplify.Auth.confirmSignIn( + challengeResponse = AuthFactorType.WEB_AUTHN.challengeResponse, + options = options + ) + Log.i("AuthQuickstart", "Next step for sign in is ${result.nextStep}") +} catch (error: AuthException) { + Log.e("AuthQuickstart", "Sign in failed", error) +} +``` + + + + +```java +AuthConfirmSignInOptions options = AWSCognitoAuthConfirmSignInOptions.builder() + .callingActivity(activity) + .build(); +RxAmplify.Auth.confirmSignIn(AuthFactorType.WEB_AUTHN.getChallengeResponse(), options) + .subscribe( + result -> Log.i("AuthQuickstart", "Next step for sign in is " + result.getNextStep()), + error -> Log.e("AuthQuickstart", "Failed to confirm sign in", error)) + ); +``` + + + + +## USER_PASSWORD_AUTH flow + +A use case for the `USER_PASSWORD_AUTH` authentication flow is migrating users into Amazon Cognito + +A user migration Lambda trigger helps migrate users from a legacy user management system into your user pool. If you choose the USER_PASSWORD_AUTH authentication flow, users don't have to reset their passwords during user migration. This flow sends your user's password to the service over an encrypted SSL connection during authentication. + +When you have migrated all your users, switch flows to the more secure SRP flow. The SRP flow doesn't send any passwords over the network. + + + + +```java +AuthSignInOptions options = AWSCognitoAuthSignInOptions.builder() + .authFlowType(AuthFlowType.USER_PASSWORD_AUTH) + .build(); +Amplify.Auth.signIn( + "hello@example.com", + "password", + options, + result -> Log.i("AuthQuickStart", "Sign in succeeded with result " + result), + error -> Log.e("AuthQuickStart", "Failed to sign in", error) +); +``` + + + + +```kotlin +val options = AWSCognitoAuthSignInOptions.builder() + .authFlowType(AuthFlowType.USER_PASSWORD_AUTH) + .build() +Amplify.Auth.signIn( + "hello@example.com", + "password", + options, + { result -> + Log.i("AuthQuickstart", "Next step for sign in is ${result.nextStep}") + }, + { error -> + Log.e("AuthQuickstart", "Failed to sign in", error) + } +) +``` + + + + +```kotlin +try { + val options = AWSCognitoAuthSignInOptions.builder() + .authFlowType(AuthFlowType.USER_PASSWORD_AUTH) + .build() + val result = Amplify.Auth.signIn( + username = "hello@example.com", + password = "password", + options = options + ) + Log.i("AuthQuickstart", "Next step for sign in is ${result.nextStep}") +} catch (error: AuthException) { + Log.e("AuthQuickstart", "Sign in failed", error) +} +``` + + + + +```java +AuthSignInOptions options = AWSCognitoAuthSignInOptions.builder() + .authFlowType(AuthFlowType.USER_PASSWORD_AUTH) + .build(); +RxAmplify.Auth.signIn("hello@example.com", "password", options) + .subscribe( + result -> Log.i("AuthQuickstart", "Next step for sign in is " + result.getNextStep()), + error -> Log.e("AuthQuickstart", error.toString()) + ); +``` + + + + +### Set up auth backend + +In order to use the authentication flow `USER_PASSWORD_AUTH`, your Cognito app client has to be configured to allow it. Amplify Gen 2 enables SRP auth by default. To enable USER_PASSWORD_AUTH, you can update the `backend.ts` file with the following changes: + +```ts title="amplify/backend.ts" +import { defineBackend } from '@aws-amplify/backend' +import { auth } from './auth/resource' +import { data } from './data/resource' + +const backend = defineBackend({ + auth, + data, +}); + +// highlight-start +backend.auth.resources.cfnResources.cfnUserPoolClient.explicitAuthFlows = [ + "ALLOW_USER_PASSWORD_AUTH", + "ALLOW_USER_SRP_AUTH", + "ALLOW_USER_AUTH", + "ALLOW_REFRESH_TOKEN_AUTH" +]; +// highlight-end +``` + +### Migrate users with Amazon Cognito + +Amazon Cognito provides a trigger to migrate users from your existing user directory seamlessly into Cognito. You achieve this by configuring your User Pool's "Migration" trigger which invokes a Lambda function whenever a user that does not already exist in the user pool authenticates, or resets their password. + +In short, the Lambda function will validate the user credentials against your existing user directory and return a response object containing the user attributes and status on success. An error message will be returned if an error occurs. There's documentation around [how to set up this migration flow](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-import-using-lambda.html) and more detailed instructions on [how the lambda should handle request and response objects](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-migrate-user.html#cognito-user-pools-lambda-trigger-syntax-user-migration). + +## CUSTOM_AUTH flow + +Amazon Cognito User Pools supports customizing the authentication flow to enable custom challenge types, in addition to a password in order to verify the identity of users. The custom authentication flow is a series of challenge and response cycles that can be customized to meet different requirements. These challenge types may include CAPTCHAs or dynamic challenge questions. + +To define your challenges for custom authentication flow, you need to implement three Lambda triggers for Amazon Cognito. + +The flow is initiated by calling `signIn` with `AWSCognitoAuthSignInOptions` configured with `AuthFlowType.CUSTOM_AUTH_WITH_SRP` OR `AuthFlowType.CUSTOM_AUTH_WITHOUT_SRP`. + +Follow the instructions in [Custom Auth Sign In](/gen1/[platform]/build-a-backend/auth/sign-in-custom-flow/) to learn about how to integrate custom authentication flow in your application with the Auth APIs. + + + + For more information about working with Lambda Triggers for custom authentication challenges, please visit [Amazon Cognito Developer Documentation](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-challenge.html). 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 1f31f20593b..af358d68c50 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 @@ -55,7 +55,55 @@ await associateWebAuthnCredential(); -{/* */} + + + +```java +Amplify.Auth.associateWebAuthnCredential( + activity, + () -> Log.i("AuthQuickstart", "Associated credential"), + error -> Log.e("AuthQuickstart", "Failed to register credential", error) +); +``` + + + + +```kotlin +Amplify.Auth.associateWebAuthnCredential( + activity, + { Log.i("AuthQuickstart", "Associated credential") }, + { Log.e("AuthQuickstart", "Failed to register credential", error) } +) +``` + + + + +```kotlin +try { + val result = Amplify.Auth.associateWebAuthnCredential(activity) + Log.i("AuthQuickstart", "Associated credential") +} catch (error: AuthException) { + Log.e("AuthQuickstart", "Failed to associate credential", error) +} +``` + + + + +```java +RxAmplify.Auth.associateWebAuthnCredential(activity) + .subscribe( + result -> Log.i("AuthQuickstart", "Associated credential"), + error -> Log.e("AuthQuickstart", "Failed to associate credential", error) + ); +``` + + + + +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). @@ -94,8 +142,8 @@ func associateWebAuthNCredentials() -> AnyCancellable { - -You will be prompted to register a passkey using your local authenticator. Amplify will then associate that passkey with Cognito. + +The user will be prompted to register a passkey using their local authenticator. Amplify will then associate that passkey with Cognito. ## List WebAuthN credentials @@ -116,11 +164,6 @@ for (const credential of result.credentials) { } ``` - - - -{/* */} - @@ -186,6 +229,76 @@ func listWebAuthNCredentials() -> AnyCancellable { + + + + + + +```java +Amplify.Auth.listWebAuthnCredentials( + result -> result.getCredentials().forEach(credential -> { + Log.i("AuthQuickstart", "Credential ID: " + credential.getCredentialId()); + Log.i("AuthQuickstart", "Friendly Name: " + credential.getFriendlyName()); + Log.i("AuthQuickstart", "Relying Party ID: " + credential.getRelyingPartyId()); + Log.i("AuthQuickstart", "Created At: " + credential.getCreatedAt()); + }), + error -> Log.e("AuthQuickstart", "Failed to list credentials", error) +); +``` + + + + +```kotlin +Amplify.Auth.listWebAuthnCredentials( + { result -> + result.credentials.forEach { credential -> + Log.i("AuthQuickstart", "Credential ID: ${credential.credentialId}") + Log.i("AuthQuickstart", "Friendly Name: ${credential.friendlyName}") + Log.i("AuthQuickstart", "Relying Party ID: ${credential.relyingPartyId}") + Log.i("AuthQuickstart", "Created At: ${credential.createdAt}") + } + }, + { error -> Log.e("AuthQuickstart", "Failed to list credentials", error) } +) +``` + + + + +```kotlin +try { + val result = Amplify.Auth.listWebAuthnCredentials() + result.credentials.forEach { credential -> + Log.i("AuthQuickstart", "Credential ID: ${credential.credentialId}") + Log.i("AuthQuickstart", "Friendly Name: ${credential.friendlyName}") + Log.i("AuthQuickstart", "Relying Party ID: ${credential.relyingPartyId}") + Log.i("AuthQuickstart", "Created At: ${credential.createdAt}") + } +} catch (error: AuthException) { + Log.e("AuthQuickstart", "Failed to list credentials", error) +} +``` + + + + +```java +RxAmplify.Auth.listWebAuthnCredentials() + .subscribe( + result -> result.getCredentials().forEach(credential -> { + Log.i("AuthQuickstart", "Credential ID: " + credential.getCredentialId()); + Log.i("AuthQuickstart", "Friendly Name: " + credential.getFriendlyName()); + Log.i("AuthQuickstart", "Relying Party ID: " + credential.getRelyingPartyId()); + Log.i("AuthQuickstart", "Created At: " + credential.getCreatedAt()); + }), + error -> Log.e("AuthQuickstart", "Failed to list credentials", error) + ); +``` + + + ## Delete WebAuthN credentials @@ -203,11 +316,6 @@ await deleteWebAuthnCredential({ credentialId: id }); ``` - - - -{/* */} - @@ -244,6 +352,59 @@ func deleteWebAuthNCredentials(credentialId: String) -> AnyCancellable { + + + + + +```java +Amplify.Auth.deleteWebAuthnCredential( + credentialId, + (result) -> Log.i("AuthQuickstart", "Deleted credential"), + error -> Log.e("AuthQuickstart", "Failed to delete credential", error) +); +``` + + + + +```kotlin +Amplify.Auth.deleteWebAuthnCredential( + credentialId, + { Log.i("AuthQuickstart", "Deleted credential") }, + { Log.e("AuthQuickstart", "Failed to delete credential", error) } +) +``` + + + + +```kotlin +try { + val result = Amplify.Auth.deleteWebAuthnCredential(credentialId) + Log.i("AuthQuickstart", "Deleted credential") +} catch (error: AuthException) { + Log.e("AuthQuickstart", "Failed to delete credential", error) +} +``` + + + + +```java +RxAmplify.Auth.deleteWebAuthnCredential(credentialId) + .subscribe( + result -> Log.i("AuthQuickstart", "Deleted credential"), + error -> Log.e("AuthQuickstart", "Failed to delete credential", error) + ); +``` + + + + +The delete passkey API has only the required `credentialId` as input, and it does not return a value. + + From a3574739cc03001c49a6a749cac0788699428722 Mon Sep 17 00:00:00 2001 From: AllanZhengYP Date: Wed, 4 Dec 2024 00:08:15 -0800 Subject: [PATCH 06/25] docs(js): add durability options (#8133) * docs(js): add durability options * chore: add copy example for expected bucket owner --- .../build-a-backend/storage/copy-files/index.mdx | 9 +++++++-- .../build-a-backend/storage/download-files/index.mdx | 4 ++++ .../build-a-backend/storage/list-files/index.mdx | 1 + .../build-a-backend/storage/remove-files/index.mdx | 1 + .../build-a-backend/storage/upload-files/index.mdx | 8 +++++++- 5 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/pages/[platform]/build-a-backend/storage/copy-files/index.mdx b/src/pages/[platform]/build-a-backend/storage/copy-files/index.mdx index ea7a3fc485c..c08e79a6601 100644 --- a/src/pages/[platform]/build-a-backend/storage/copy-files/index.mdx +++ b/src/pages/[platform]/build-a-backend/storage/copy-files/index.mdx @@ -86,7 +86,8 @@ const copyFile = async () => { path: 'album/2024/1.jpg', // Specify a target bucket using name assigned in Amplify Backend // or bucket name from console and associated region - bucket: 'assignedNameInAmplifyBackend' + bucket: 'assignedNameInAmplifyBackend', + expectedBucketOwner: '123456789012' }, destination: { path: 'shared/2024/1.jpg', @@ -95,7 +96,8 @@ const copyFile = async () => { bucket: { bucketName: 'generated-second-bucket-name', region: 'us-east-2' - } + }, + expectedBucketOwner: '123456789013' } }); } catch (error) { @@ -114,6 +116,9 @@ Option | Type | Default | Description | | -- | :--: | :--: | ----------- | | path | string \|
(\{ identityId \}) => string | Required | A string or callback that represents the path in source and destination bucket to copy the object to or from.
**Each segment of the path in `source` must by URI encoded.** | | bucket | string \|
\{ bucketName: string;
region: string; \} | Default bucket and region from Amplify configuration | A string representing the target bucket's assigned name in Amplify Backend or an object specifying the bucket name and region from the console.

Read more at [Configure additional storage buckets](/[platform]/build-a-backend/storage/set-up-storage/#configure-additional-storage-buckets). | +| eTag | string | Optional | The copy **source object** entity tag (ETag) value. Only Copies the object if its ETag matches the specified tag. | +| notModifiedSince | Date | Optional | Copies the **source object** if it hasn't been modified since the specified time.

**This is evaluated only when `eTag` is NOT supplied**| +| expectedBucketOwner | string | Optional | `source.expectedBucketOwner`: The account ID that owns the source bucket.

`destination.expectedBucketOwner`: The account ID that owns the destination bucket. |
diff --git a/src/pages/[platform]/build-a-backend/storage/download-files/index.mdx b/src/pages/[platform]/build-a-backend/storage/download-files/index.mdx index 9c0c57daad8..2b7b190d8ee 100644 --- a/src/pages/[platform]/build-a-backend/storage/download-files/index.mdx +++ b/src/pages/[platform]/build-a-backend/storage/download-files/index.mdx @@ -106,6 +106,8 @@ const linkToStorageFile = await getUrl({ expiresIn: 300, // whether to use accelerate endpoint useAccelerateEndpoint: true, + // The account ID that owns the requested bucket. + expectedBucketOwner: '123456789012', } }); ``` @@ -116,6 +118,7 @@ Option | Type | Default | Description | | validateObjectExistence | boolean | false | Whether to head object to make sure the object existence before downloading. | | expiresIn | number | 900 | Number of seconds till the URL expires.

The expiration time of the presigned url is dependent on the session and will max out at 1 hour. | | useAccelerateEndpoint | boolean | false | Whether to use accelerate endpoint.

Read more at [Transfer Acceleration](/[platform]/build-a-backend/storage/extend-s3-resources/#example---enable-transfer-acceleration) | +| expectedBucketOwner | string | Optional | The account ID that owns requested bucket. | @@ -1188,6 +1191,7 @@ Option | Type | Default | Description | | onProgress | callback | — | Callback function tracking the upload/download progress. | | bytesRange | \{ start: number; end:number; \} | — | Bytes range parameter to download a part of the file. | | useAccelerateEndpoint | boolean | false | Whether to use accelerate endpoint.

Read more at [Transfer Acceleration](/[platform]/build-a-backend/storage/extend-s3-resources/#example---enable-transfer-acceleration) | +| expectedBucketOwner | string | Optional | The account ID that owns requested bucket. | ## Frequently Asked Questions diff --git a/src/pages/[platform]/build-a-backend/storage/list-files/index.mdx b/src/pages/[platform]/build-a-backend/storage/list-files/index.mdx index 546124747a9..13c3370a014 100644 --- a/src/pages/[platform]/build-a-backend/storage/list-files/index.mdx +++ b/src/pages/[platform]/build-a-backend/storage/list-files/index.mdx @@ -240,6 +240,7 @@ const result = await list({ | nextToken | string | — | Indicates whether the list is being continued on this bucket with a token | | subpathStrategy | \{ strategy: 'include' \} \|
\{ 'exclude',
delimiter?: string \} | \{ strategy: 'include' \} | An object representing the subpath inclusion strategy and the delimiter used to group results for exclusion.

Read more at [Excluding subpaths](/[platform]/build-a-backend/storage/list-files/#excluding-subpaths) | | useAccelerateEndpoint | boolean | false | Whether to use accelerate endpoint.

Read more at [Transfer Acceleration](/[platform]/build-a-backend/storage/extend-s3-resources/#example---enable-transfer-acceleration) | +| expectedBucketOwner | string | Optional | The account ID that owns requested bucket. | diff --git a/src/pages/[platform]/build-a-backend/storage/remove-files/index.mdx b/src/pages/[platform]/build-a-backend/storage/remove-files/index.mdx index 27e081ec6eb..0ee372de282 100644 --- a/src/pages/[platform]/build-a-backend/storage/remove-files/index.mdx +++ b/src/pages/[platform]/build-a-backend/storage/remove-files/index.mdx @@ -389,4 +389,5 @@ Future remove() async { Option | Type | Default | Description | | -- | :--: | :--: | ----------- | | bucket | string \|
\{ bucketName: string;
region: string; \} | Default bucket and region from Amplify configuration | A string representing the target bucket's assigned name in Amplify Backend or an object specifying the bucket name and region from the console.

Read more at [Configure additional storage buckets](/[platform]/build-a-backend/storage/set-up-storage/#configure-additional-storage-buckets) | +| expectedBucketOwner | string | Optional | The account ID that owns requested bucket. | diff --git a/src/pages/[platform]/build-a-backend/storage/upload-files/index.mdx b/src/pages/[platform]/build-a-backend/storage/upload-files/index.mdx index ce8b9c66477..a4f264a9491 100644 --- a/src/pages/[platform]/build-a-backend/storage/upload-files/index.mdx +++ b/src/pages/[platform]/build-a-backend/storage/upload-files/index.mdx @@ -1566,7 +1566,11 @@ const result = await uploadData({ // configure how object is presented contentDisposition: "attachment", // whether to use accelerate endpoint - useAccelerateEndpoint: true + useAccelerateEndpoint: true, + // the account ID that owns requested bucket + expectedBucketOwner: '123456789012', + // whether to check if an object with the same key already exists before completing the upload + preventOverwrite: true, }, }); ``` @@ -1578,6 +1582,8 @@ Option | Type | Default | Description | | contentDisposition | string | — | Specifies presentational information for the object.

Read more at [Content-Disposition documentation](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition) | | metadata | map\ | — | A map of metadata to store with the object in S3.

Read more at [S3 metadata documentation](https://docs.aws.amazon.com/AmazonS3/latest/userguide/UsingMetadata.html#UserMetadata) | | useAccelerateEndpoint | boolean | false | Whether to use accelerate endpoint.

Read more at [Transfer Acceleration](/[platform]/build-a-backend/storage/upload-files/#transfer-acceleration) | +| expectedBucketOwner | string | - | The account ID that owns requested bucket. | +| preventOverwrite | boolean | false | Whether to check if an object with the same key already exists before completing the upload. If exists, a `Precondition Failed` error will be thrown | From 9e1687c3d5c78c595921a3dcdd7725e93f392ec3 Mon Sep 17 00:00:00 2001 From: Mo Malaka Date: Wed, 4 Dec 2024 12:04:14 -0800 Subject: [PATCH 07/25] Q workspace command (#8147) * Q workspace command * Update src/pages/[platform]/build-a-backend/q-developer/index.mdx Co-authored-by: Arundeep Nagaraj <84913786+arundna@users.noreply.github.com> * Update src/pages/[platform]/build-a-backend/q-developer/index.mdx Co-authored-by: Arundeep Nagaraj <84913786+arundna@users.noreply.github.com> * Update src/pages/[platform]/build-a-backend/q-developer/index.mdx Co-authored-by: Arundeep Nagaraj <84913786+arundna@users.noreply.github.com> * Update src/pages/[platform]/build-a-backend/q-developer/index.mdx Co-authored-by: Arundeep Nagaraj <84913786+arundna@users.noreply.github.com> * closing tag * Update src/pages/[platform]/build-a-backend/q-developer/index.mdx Co-authored-by: James Jarvis --------- Co-authored-by: Arundeep Nagaraj <84913786+arundna@users.noreply.github.com> Co-authored-by: James Jarvis --- cspell.json | 3 +- .../images/gen2/q-developer/authentication.md | 219 +++++++++++++++ public/images/gen2/q-developer/general.md | 6 + .../q-developer/modeling-relationships.md | 262 ++++++++++++++++++ .../gen2/q-developer/modeling-schema.md | 185 +++++++++++++ .../build-a-backend/q-developer/index.mdx | 33 +++ 6 files changed, 707 insertions(+), 1 deletion(-) create mode 100644 public/images/gen2/q-developer/authentication.md create mode 100644 public/images/gen2/q-developer/general.md create mode 100644 public/images/gen2/q-developer/modeling-relationships.md create mode 100644 public/images/gen2/q-developer/modeling-schema.md diff --git a/cspell.json b/cspell.json index c2c0df0f66b..49baa6ee1e1 100644 --- a/cspell.json +++ b/cspell.json @@ -1617,7 +1617,8 @@ "webauthn", "knowledgebases", "rehype", - "assetlinks" + "assetlinks", + "AMPLIFYRULES" ], "flagWords": ["hte", "full-stack", "Full-stack", "Full-Stack", "sudo"], "patterns": [ diff --git a/public/images/gen2/q-developer/authentication.md b/public/images/gen2/q-developer/authentication.md new file mode 100644 index 00000000000..2ec836d9794 --- /dev/null +++ b/public/images/gen2/q-developer/authentication.md @@ -0,0 +1,219 @@ +# AMPLIFYRULES + +- RULES THAT SHOULD BE ADHERED TO THE LAST WORD. + + 1. EXTERNAL PROVIDERS THAT ARE AVAILABLE ARE LISTED BELOW IN THE EXAMPLE, DON'T CHANGE THE NAMING CONVENTION WHILE USING THOSE IN THE CODE GENERATION. + 2. DON'T FORGET TO IMPORT SECRET FOR ANY AUTHENTICATION BASED QUESTION. + + ```typescript + import { defineAuth, secret } from "@aws-amplify/backend"; + ``` + + 3. CALLBACK AND LOGOUT URLS SHOULD BE INSIDE THE "EXTERNALPROVIDERS" OBJECT. + 4. WHILE ADDING THE CUSTOM ATTRIBUTES, IF THE ATTRIBUTE YOU ARE ADDING DOESNT BELONG TO THE STANDARD USER ATTRIBUTES LIST THEN ADD IT AS A CUSTOM ATTRIBUTE LIKE THIS "CUSTOM:ATTRIBUTE_NAME" AND THIS DOESN'T SUPPORT "REQUIRED" FIELD SO IGNORE IT WHILE GENERATING THE ANSWER. + 5. WHILE ADDING THE CUSTOM ATTRIBUTES, MAKE SURE TO ALWAYS ADD THE "DATATYPE" FIELD AS IT IS A REQUIRED FIELD. + 6. STATNDARD ATTIBUTES THAT ARE ALLOWED: `familyName`, `giveName`, `middleName`, `nickname`, `preferredUsername`, `profile`, `profilePicture`, `website`, `gender`, `birthdate`, `zoneinfo`, `locale`, `updatedAt`, `address`, `email`, `phoneNumber`, `sub`. THE `userAttributes` ARE SUPPOSED TO BE OUTSIDE THE `loginWith` OBJECT + + 7. THE FOLLOWING IS THE REQUIRED SYNTAX FOR `externalProviders`. ONLY THE FOUR LISTED PROVIDERS BELOW ARE SUPPORTED: + + ```typescript + loginWith:{ + //loginMethods + externalProviders: { + google: { + + }, + signInWithApple: { + }, + loginWithAmazon: { + + }, + facebook: { + }, + callbackUrls: [ + // Callback URLs should be included inside the `externalProviders` object only, as per rule. + + ], + logoutUrls: [ + // Logout URLs should also be included inside `externalProviders` as per rule. + + ], + }, + } + ``` + + 8. THE `userAttributes` ARE SUPPOSED TO BE OUTSIDE THE `loginWith` OBJECT + `// Example configuration for user attributes and login methods +loginWith: { + // Specify login methods separately from user attributes, ONLY MENTION THE LOGINMETHODS THAT ARE SUPPOSED TO BE TRUE + email: true, // Enable login with email + phone: false, // Disable login with phone number +},userAttributes: { + email: true, // Attribute set as required + phoneNumber: false, // Attribute not required + givenName: true, // Attribute set as required +},` + 9. THIS IS THE WAY TO SETUP THE MULTIFACTOR AUTHENTHICATION + export const auth = defineAuth({ + loginWith: { + email: true, + phone: true, + }, + multifactor: { + mode: "OPTIONAL", + sms: true, + totp: false, + }, + // IMPORTANT! THE LOGIC TO RESOLVE THIS VALUE CANNOT DETERMINE WHETHER EMAIL MFA IS ENABLED WHEN OVERRIDING THE RESOURCE. + // BE SURE TO PICK A RECOVERY OPTION APPROPRIATE FOR YOUR APPLICATION. + accountRecovery: "EMAIL_AND_PHONE_WITHOUT_MFA", + senders: { + email: { + fromEmail: "registrations@example.com", + }, + }, + }) + 10. DON'T ADD THE `passwordPolicy` IN THE AUTH/RESOURCE.TS FILE. + 11. THE `username` SHOULD BE REPLACED WITH `userAttributes` WHEN REQUESTED TO SETUP USERNAME AS A LOGIN METHOD AS USERNAME IS NOT A VALID LOGIN METHOD ON DEFINEAUTH, FOR EXAMPLE: + + ```typescript + userAttributes: { + preferredUsername: { + mutable: true, + required: false + } + } + ``` + + 12. `loginWith` SUPPORTS ONLY TWO METHODS THAT IS `email` and `phone`. THERE IS NO `username` attribute to that. + 13. THE `callbackUrls` AND `logoutUrls` SHOULD ONLY BE MENTIONED ONCE AS MENTIONED IN RULE #7 AND NOT FOR EACH EXTERNAL PROVIDER + +```typescript +import { defineAuth, secret } from "@aws-amplify/backend"; + +export const auth = defineAuth({ + // Login Methods Configuration + loginWith: { + // Only email and phone are supported as login methods + email: true, + phone: true, + + // External Providers Configuration - all providers shown with required fields + externalProviders: { + // Google Authentication + google: { + clientId: secret("GOOGLE_CLIENT_ID"), + clientSecret: secret("GOOGLE_CLIENT_SECRET"), + }, + // Sign in with Apple + signInWithApple: { + clientId: secret("SIWA_CLIENT_ID"), + keyId: secret("SIWA_KEY_ID"), + privateKey: secret("SIWA_PRIVATE_KEY"), + teamId: secret("SIWA_TEAM_ID"), + }, + // Login with Amazon + loginWithAmazon: { + clientId: secret("LOGINWITHAMAZON_CLIENT_ID"), + clientSecret: secret("LOGINWITHAMAZON_CLIENT_SECRET"), + }, + // Facebook Authentication + facebook: { + clientId: secret("FACEBOOK_CLIENT_ID"), + clientSecret: secret("FACEBOOK_CLIENT_SECRET"), + }, + // Callback and logout URLs must be inside externalProviders + callbackUrls: [ + "http://localhost:3000/profile", + "https://mywebsite.com/profile", + ], + logoutUrls: ["http://localhost:3000/", "https://mywebsite.com"], + }, + }, + + // User Attributes Configuration - outside loginWith + userAttributes: { + // Standard attributes examples + email: { + mutable: true, + required: true, + }, + phoneNumber: { + mutable: true, + required: false, + }, + givenName: { + mutable: true, + required: true, + }, + familyName: { + mutable: true, + required: false, + }, + birthdate: { + mutable: true, + required: false, + }, + // Username configuration using preferredUsername + preferredUsername: { + mutable: true, + required: false, + }, + // Additional standard attributes + address: { + mutable: true, + required: false, + }, + gender: { + mutable: true, + required: false, + }, + locale: { + mutable: true, + required: false, + }, + profilePicture: { + mutable: true, + required: false, + }, + website: { + mutable: true, + required: false, + }, + // Custom attributes examples - note the 'custom:' prefix and required dataType + "custom:organization": { + dataType: "String", + mutable: true, + minLen: 3, + maxLen: 100, + }, + "custom:employeeId": { + dataType: "Number", + mutable: false, + min: 1000, + max: 9999999, + }, + "custom:isVerified": { + dataType: "Boolean", + mutable: true, + }, + }, + + // Multi-factor Authentication Configuration + multifactor: { + mode: "OPTIONAL", // Can be OPTIONAL or REQUIRED + sms: true, + totp: false, + }, + + // Account Recovery Configuration + accountRecovery: "EMAIL_AND_PHONE_WITHOUT_MFA", + + // Email Sender Configuration + senders: { + email: { + fromEmail: "registrations@example.com", + }, + }, +}); +``` diff --git a/public/images/gen2/q-developer/general.md b/public/images/gen2/q-developer/general.md new file mode 100644 index 00000000000..7dd16a2396a --- /dev/null +++ b/public/images/gen2/q-developer/general.md @@ -0,0 +1,6 @@ +# AMPLIFYRULES + +1. ALWAYS MENTION WHICH FILE PATH THE USER NEED TO PASTE THE CODE IN. +2. MAKE SURE IF IT IS IN TWO DIFFERENT FILES, YOU DIVIDE THE CODE SNIPPET AND MENTION THE FILE PATH +3. IF THE FILE DOESN'T ALREADY EXIST, GIVE THE STEPS TO GENERATE THE FILES. +4. COMMENT ON EVERY PIECE OF CODE THAT CAN HELP IMPROVE CODE QUALITY THAT EXPLAINS WHAT THE LINE DOES diff --git a/public/images/gen2/q-developer/modeling-relationships.md b/public/images/gen2/q-developer/modeling-relationships.md new file mode 100644 index 00000000000..fc67053fb50 --- /dev/null +++ b/public/images/gen2/q-developer/modeling-relationships.md @@ -0,0 +1,262 @@ +# AMPLIFYRULES + +title: Modeling relationships - AWS Amplify Gen 2 Documentation +source: https://docs.amplify.aws/typescript/build-a-backend/data/data-modeling/relationships/ +framework: typescript +lastModified: 2024-10-21T23:11:46.997Z + +--- + +WHEN MODELING APPLICATION DATA, YOU OFTEN NEED TO ESTABLISH RELATIONSHIPS BETWEEN DIFFERENT DATA MODELS. IN AMPLIFY DATA, YOU CAN CREATE ONE-TO-MANY, ONE-TO-ONE, AND MANY-TO-MANY RELATIONSHIPS IN YOUR DATA SCHEMA. ON THE CLIENT-SIDE, AMPLIFY DATA ALLOWS YOU TO LAZY OR EAGER LOAD OF RELATED DATA. + +```typescript +const schema = a + .schema({ + Member: a.model({ + name: a.string().required(), // 1. Create a reference field teamId: a.id(), + // 2. Create a belongsTo relationship with the reference field + team: a.belongsTo("Team", "teamId"), + }), + Team: a.model({ + mantra: a.string().required(), // 3. Create a hasMany relationship with the reference field + // from the `Member`s model. + members: a.hasMany("Member", "teamId"), + }), + }) + .authorization((allow) => allow.publicApiKey()); +``` + +CREATE A "HAS MANY" RELATIONSHIP BETWEEN RECORDS + +```typescript +const { data: team } = await client.models.Team.create({ + mantra: "Go Frontend!", +}); +const { data: member } = await client.models.Member.create({ + name: "Tim", + teamId: team.id, +}); +``` + +UPDATE A "HAS MANY" RELATIONSHIP BETWEEN RECORDS + +```typescript +const { data: newTeam } = await client.models.Team.create({ + mantra: "Go Fullstack", +}); +await client.models.Member.update({ id: "MY_MEMBER_ID", teamId: newTeam.id }); +``` + +DELETE A "HAS MANY" RELATIONSHIP BETWEEN RECORDS +IF YOUR REFERENCE FIELD IS NOT REQUIRED, THEN YOU CAN "DELETE" A ONE-TO-MANY RELATIONSHIP BY SETTING THE RELATIONSHIP VALUE TO NULL. + +```typescript +await client.models.Member.update({ id: "MY_MEMBER_ID", teamId: null }); +``` + +LAZY LOAD A "HAS MANY" RELATIONSHIP + +```typescript +const { data: team } = await client.models.Team.get({ id: "MY_TEAM_ID" }); +const { data: members } = await team.members(); +members.forEach((member) => console.log(member.id)); +``` + +EAGERLY LOAD A "HAS MANY" RELATIONSHIP + +```typescript +const { data: teamWithMembers } = await client.models.Team.get( + { id: "MY_TEAM_ID" }, + { selectionSet: ["id", "members.*"] } +); +teamWithMembers.members.forEach((member) => console.log(member.id)); +``` + +```typescript +const schema = a + .schema({ + Cart: a.model({ + items: a.string().required().array(), + // 1. Create reference field + customerId: a.id(), + // 2. Create relationship field with the reference field + customer: a.belongsTo("Customer", "customerId"), + }), + Customer: a.model({ + name: a.string(), + // 3. Create relationship field with the reference field + // from the Cart model + activeCart: a.hasOne("Cart", "customerId"), + }), + }) + .authorization((allow) => allow.publicApiKey()); +``` + +CREATE A "HAS ONE" RELATIONSHIP BETWEEN RECORDS +TO CREATE A "HAS ONE" RELATIONSHIP BETWEEN RECORDS, FIRST CREATE THE PARENT ITEM AND THEN CREATE THE CHILD ITEM AND ASSIGN THE PARENT. + +```typescript +const { data: customer, errors } = await client.models.Customer.create({ + name: "Rene", +}); + +const { data: cart } = await client.models.Cart.create({ + items: ["Tomato", "Ice", "Mint"], + customerId: customer?.id, +}); +``` + +UPDATE A "HAS ONE" RELATIONSHIP BETWEEN RECORDS +TO UPDATE A "HAS ONE" RELATIONSHIP BETWEEN RECORDS, YOU FIRST RETRIEVE THE CHILD ITEM AND THEN UPDATE THE REFERENCE TO THE PARENT TO ANOTHER PARENT. FOR EXAMPLE, TO REASSIGN A CART TO ANOTHER CUSTOMER: + +```typescript +const { data: newCustomer } = await client.models.Customer.create({ + name: "Ian", +}); +await client.models.Cart.update({ id: cart.id, customerId: newCustomer?.id }); +``` + +DELETE A "HAS ONE" RELATIONSHIP BETWEEN RECORDS +YOU CAN SET THE RELATIONSHIP FIELD TO NULL TO DELETE A "HAS ONE" RELATIONSHIP BETWEEN RECORDS. + +```typescript +await client.models.Cart.update({ id: project.id, customerId: null }); +``` + +LAZY LOAD A "HAS ONE" RELATIONSHIP + +```typescript +const { data: cart } = await client.models.Cart.get({ id: "MY_CART_ID" }); +const { data: customer } = await cart.customer(); +``` + +EAGERLY LOAD A "HAS ONE" RELATIONSHIP + +```typescript +const { data: cart } = await client.models.Cart.get( + { id: "MY_CART_ID" }, + { selectionSet: ["id", "customer.*"] } +); +console.log(cart.customer.id); +``` + +MODEL A "MANY-TO-MANY" RELATIONSHIP +IN ORDER TO CREATE A MANY-TO-MANY RELATIONSHIP BETWEEN TWO MODELS, YOU HAVE TO CREATE A MODEL THAT SERVES AS A "JOIN TABLE". THIS "JOIN TABLE" SHOULD CONTAIN TWO ONE-TO-MANY RELATIONSHIPS BETWEEN THE TWO RELATED ENTITIES. FOR EXAMPLE, TO MODEL A POST THAT HAS MANY TAGS AND A TAG HAS MANY POSTS, YOU'LL NEED TO CREATE A NEW POSTTAG MODEL THAT RETYPESCRIPTSENTS THE RELATIONSHIP BETWEEN THESE TWO ENTITIES. + +```typescript +const schema = a + .schema({ + PostTag: a.model({ + // 1. Create reference fields to both ends of + // the many-to-many relationshipCopy highlighted code example + postId: a.id().required(), + tagId: a.id().required(), + // 2. Create relationship fields to both ends of + // the many-to-many relationship using their + // respective reference fieldsCopy highlighted code example + post: a.belongsTo("Post", "postId"), + tag: a.belongsTo("Tag", "tagId"), + }), + Post: a.model({ + title: a.string(), + content: a.string(), + // 3. Add relationship field to the join model + // with the reference of `postId`Copy highlighted code example + tags: a.hasMany("PostTag", "postId"), + }), + Tag: a.model({ + name: a.string(), + // 4. Add relationship field to the join model + // with the reference of `tagId`Copy highlighted code example + posts: a.hasMany("PostTag", "tagId"), + }), + }) + .authorization((allow) => allow.publicApiKey()); +``` + +MODEL MULTIPLE RELATIONSHIPS BETWEEN TWO MODELS +RELATIONSHIPS ARE DEFINED UNIQUELY BY THEIR REFERENCE FIELDS. FOR EXAMPLE, A POST CAN HAVE SEPARATE RELATIONSHIPS WITH A PERSON MODEL FOR AUTHOR AND EDITOR. + +```typescript +const schema = a + .schema({ + Post: a.model({ + title: a.string().required(), + content: a.string().required(), + authorId: a.id(), + author: a.belongsTo("Person", "authorId"), + editorId: a.id(), + editor: a.belongsTo("Person", "editorId"), + }), + Person: a.model({ + name: a.string(), + editedPosts: a.hasMany("Post", "editorId"), + authoredPosts: a.hasMany("Post", "authorId"), + }), + }) + .authorization((allow) => allow.publicApiKey()); +``` + +ON THE CLIENT-SIDE, YOU CAN FETCH THE RELATED DATA WITH THE FOLLOWING CODE: + +```typescript +const client = generateClient(); +const { data: post } = await client.models.Post.get({ id: "SOME_POST_ID" }); +const { data: author } = await post?.author(); +const { data: editor } = await post?.editor(); +``` + +MODEL RELATIONSHIPS FOR MODELS WITH SORT KEYS IN THEIR IDENTIFIER +IN CASES WHERE YOUR DATA MODEL USES SORT KEYS IN THE IDENTIFIER, YOU NEED TO ALSO ADD REFERENCE FIELDS AND STORE THE SORT KEY FIELDS IN THE RELATED DATA MODEL: + +```typescript +const schema = a + .schema({ + Post: a.model({ + title: a.string().required(), + content: a.string().required(), + // Reference fields must correspond to identifier fields. + authorName: a.string(), + authorDoB: a.date(), + // Must pass references in the same order as identifiers. + author: a.belongsTo("Person", ["authorName", "authorDoB"]), + }), + Person: a + .model({ + name: a.string().required(), + dateOfBirth: a.date().required(), + // Must reference all reference fields corresponding to the + // identifier of this model. + authoredPosts: a.hasMany("Post", ["authorName", "authorDoB"]), + }) + .identifier(["name", "dateOfBirth"]), + }) + .authorization((allow) => allow.publicApiKey()); +``` + +MAKE RELATIONSHIPS REQUIRED OR OPTIONAL +AMPLIFY DATA'S RELATIONSHIPS USE REFERENCE FIELDS TO DETERMINE IF A RELATIONSHIP IS REQUIRED OR NOT. IF YOU MARK A REFERENCE FIELD AS REQUIRED, THEN YOU CAN'T "DELETE" A RELATIONSHIP BETWEEN TWO MODELS. YOU'D HAVE TO DELETE THE RELATED RECORD AS A WHOLE. + +```typescript +const schema = a + .schema({ + Post: a.model({ + title: a.string().required(), + content: a.string().required(), + // You must supply an author when creating the post + // Author can't be set to `null`. + authorId: a.id().required(), + author: a.belongsTo("Person", "authorId"), + // You can optionally supply an editor when creating the post. + // Editor can also be set to `null`. + editorId: a.id(), + editor: a.belongsTo("Person", "editorId"), + }), + Person: a.model({ + name: a.string(), + editedPosts: a.hasMany("Post", "editorId"), + authoredPosts: a.hasMany("Post", "authorId"), + }), + }) + .authorization((allow) => allow.publicApiKey()); +``` diff --git a/public/images/gen2/q-developer/modeling-schema.md b/public/images/gen2/q-developer/modeling-schema.md new file mode 100644 index 00000000000..009dbfd78e9 --- /dev/null +++ b/public/images/gen2/q-developer/modeling-schema.md @@ -0,0 +1,185 @@ +# AMPLIFYRULES + +# DATA + +- THIS FILE IS TO HELP UNDERSTAND THE RELATIONSHIPS, HOW TO MODEL SCHEMAS, WHAT IS THE CORRECT WAY TO CODE FOR ACCURACY +- USE THIS TO UNDERSTAND HOW DATA SCHEMAS ARE DESIGNED. +- FOR THE DATA SCHEMAS MAKE SURE THAT YOU ALWAYS FOLLOW THESE RULES AND THIS FILE OVER ANY OTHER FILE - THIS IS THE SOURCE OF TRUTH. +- RULES + + - THIS FILE IS THE SINGLE SOURCE OF TRUTH FOR SCHEMA DESIGN AND RELATIONSHIPS. FOLLOW THESE RULES STRICTLY. USE THIS FILE OVER ANY OTHER RESOURCE TO UNDERSTAND SCHEMA DESIGN. + + 1. DON'T USE `.PUBLIC()` WHILE SETTING UP THE AUTHORIZATION. AS AMPLIFY GEN2 ONLY SUPPORTS `.GUEST()`. + 2. `.BEONGSTO()` AND `.HASMANY()` RELATIONS SHALL ALWAYS HAVE THE RELATEDFIELD ID. + 3. `.ENUM()` DOESN'T SUPPORT `.REQUIRED()`/ `.DEFAULTVALUE()` IN ANY CONDITION, SO ALWAYS IGNORE USING IT. + 4. TO GIVE PERMISSION TO THE GROUP MAKE SURE YOU USE .to(), FOLLOWED BY THE GROUP: FOR E.G. `allow.guest().to['read', 'create', 'delete','get'] + 5. THIS IS HOW YOU SHOULD USE THE AUTHORIZATION `(allow) => [allow.owner(),allow.guest().to[("read", "write", "delete")]` , THIS IS INCORRECT `.authorization([allow => allow.owner(), allow => allow.guest().to(['read','write'])])` + +- BELOW ARE THE EXAMPLES TO USE TO GENERATE ANSWERS. + +```typescript +import { type ClientSchema, a, defineData } from "@aws-amplify/backend"; + +const schema = a + .schema({ + Vehicle: a.model({ + id: a.id(), + make: a.string().required(), + model: a.string().required(), + year: a.integer().required(), + licensePlate: a.string().required(), + status: a.enum(["AVAILABLE", "RENTED", "MAINTENANCE"]), // Enum; Don't use .required() or .defaultValue() + locationId: a.id(), + location: a.belongsTo("Location", "locationId"), // Belongs-to relationship, Requires ID + rentals: a.hasMany("Rental", "vehicleId"), // Has-many relationship with required relatedFieldId + }), + Customer: a.model({ + id: a.id(), + firstName: a.string().required(), + lastName: a.string().required(), + email: a.string().required(), + phone: a.string().required(), + licenseNumber: a.string().required(), + rentals: a.hasMany("Rental", "customerId"), // Has-many relationship with required relatedFieldId + }), + Location: a.model({ + id: a.id(), + name: a.string().required(), + address: a.string().required(), + city: a.string().required(), + state: a.string().required(), + zipCode: a.string().required(), + vehicles: a.hasMany("Vehicle", "locationId"), // Has-many relationship with required relatedFieldId + }), + Rental: a.model({ + id: a.id(), + startDate: a.datetime().required(), + endDate: a.datetime().required(), + status: a.enum(["ACTIVE", "COMPLETED", "CANCELLED"]), // Enum; no .required() or .defaultValue() + vehicleId: a.id(), + customerId: a.id(), + vehicle: a.belongsTo("Vehicle", "vehicleId"), // Belongs-to relationship, Requires ID + customer: a.belongsTo("Customer", "customerId"), // Has-many relationship with required relatedFieldId + }), + }) + .authorization((allow) => [ + allow.owner(), + allow.guest().to[("read", "write", "delete")], + ]); // Owner-based and guest access, `.public()` references are replaced with `.guest()`. Authorizaiton groups can be concatenated, To give the permission use the to() function + +export type Schema = ClientSchema; + +export const data = defineData({ + schema, + authorizationModes: { + defaultAuthorizationMode: "apiKey", + apiKeyAuthorizationMode: { + expiresInDays: 30, + }, + }, +}); +``` + +- Another Example + +```typescript +import { type ClientSchema, a, defineData } from "@aws-amplify/backend"; + +// Define the schema for the ecommerce application +const schema = a.schema({ + Product: a + .model({ + name: a.string().required(), + description: a.string(), + price: a.float().required(), + inventory: a.integer(), + categoryId: a.id(), + category: a.belongsTo("Category", "categoryId"), // belongs to relationship with required relatedFieldId + images: a.string().array(), + }) + .authorization((allow) => [allow.guest()]), + + Category: a + .model({ + name: a.string().required(), + description: a.string(), + products: a.hasMany("Product", "categoryId"), // Has-many relationship with required relatedFieldId + }) + .authorization((allow) => [allow.guest()]), + + Order: a + .model({ + userId: a.id().required(), + status: a.enum(["PENDING", "PROCESSING", "SHIPPED", "DELIVERED"]), // Enum; Don't use .required() or .defaultValue() + total: a.float().required(), + items: a.hasMany("OrderItem", "orderId"), // Has-many relationship with required relatedFieldId + }) + .authorization((allow) => [allow.owner()]), + + OrderItem: a + .model({ + orderId: a.id().required(), + productId: a.id().required(), + quantity: a.integer().required(), + price: a.float().required(), + }) + .authorization((allow) => [allow.owner()]), +}); + +// Define the client schema and data export +export type Schema = ClientSchema; +export const data = defineData({ + schema, + authorizationModes: { + defaultAuthorizationMode: "userPool", + }, +}); +``` + +```typescript +import { type ClientSchema, a, defineData } from "@aws-amplify/backend"; + +const schema = a + .schema({ + Customer: a + .model({ + customerId: a.id().required(), + // fields can be of various scalar types, + // such as string, boolean, float, integers etc. + name: a.string(), + // fields can be of custom types + location: a.customType({ + // fields can be required or optional + lat: a.float().required(), + long: a.float().required(), + }), + // fields can be enums + engagementStage: a.enum(["PROSPECT", "INTERESTED", "PURCHASED"]), //enum doesn't support required + collectionId: a.id(), + collection: a.belongsTo("Collection", "collectionId"), + // Use custom identifiers. By default, it uses an `id: a.id()` field + }) + .identifier(["customerId"]), + Collection: a + .model({ + customers: a.hasMany("Customer", "collectionId"), // setup relationships between types + tags: a.string().array(), // fields can be arrays + representativeId: a.id().required(), + // customize secondary indexes to optimize your query performance + }) + .secondaryIndexes((index) => [index("representativeId")]), + }) + .authorization((allow) => [allow.publicApiKey()]); + +export type Schema = ClientSchema; + +export const data = defineData({ + schema, + authorizationModes: { + defaultAuthorizationMode: "apiKey", + apiKeyAuthorizationMode: { + expiresInDays: 30, + }, + }, +}); +``` diff --git a/src/pages/[platform]/build-a-backend/q-developer/index.mdx b/src/pages/[platform]/build-a-backend/q-developer/index.mdx index bc6807f33b7..bdd9e881c3b 100644 --- a/src/pages/[platform]/build-a-backend/q-developer/index.mdx +++ b/src/pages/[platform]/build-a-backend/q-developer/index.mdx @@ -90,3 +90,36 @@ import { type ClientSchema, a, defineData } from "@aws-amplify/backend"; **Step 4:** Make any required changes to the schema and save the `amplify/data/resource.ts` file. This will trigger a sandbox deployment and your new data model will be deployed + +## Use Q Developer - workspace in your Amplify project + + +Adding `@workspace` to your question in Amazon Q automatically incorporates the most relevant parts of your workspace code as context, using an index that updates periodically. For more information on `@workspace` functionality, please visit [Amazon Q Developer - Workspace Context](https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/workspace-context.html). + +The files below provide detailed guides that can be included as context with the `@workspace` command, enhancing Amazon Q's accuracy in generating AWS Amplify Gen 2 code. + +Download the files and follow the steps below to use the `@workspace` in your Amplify project + +- general.md +- authentication.md +- modeling-relationships.md +- modeling-schema.md + + +**Step 1:** Create a folder in the root of your project and give a descriptive name such as `context`. Add the files downloaded above to this folder. + +**Step 2:** Open Amazon Q Developer Chat in your IDE and type `@workspace` to enable workspace indexing. Follow Amazon Q's prompts to set up indexing for your project directory. + +**Step 3:** After successful indexing, reference the markdown file content in your queries to Amazon Q. Examples: + +```bash title="Terminal" + +@workspace follow AMPLIFYRULES to develop a data model schema for a freelance marketplace using Amplify Gen 2. Include models for freelancers, clients, projects, bids, and reviews. Use Amplify Gen 2 to fetch a list of projects + +``` + +```bash title="Terminal" + +@workspace follow AMPLIFYRULES to design a data schema for an event management application using Amplify Gen 2. Include models for users, events, and tickets. Show me how to use Amplify Gen 2 to fetch a list of events. + +``` From f14b8b932a66d7df96bfd72d42b1d886a36bd566 Mon Sep 17 00:00:00 2001 From: Ian Saultz <52051793+atierian@users.noreply.github.com> Date: Mon, 9 Dec 2024 09:31:52 -0800 Subject: [PATCH 08/25] ai kit: add table for supported models and add nova (#8148) --- .../[platform]/ai/concepts/models/index.mdx | 173 +++++++++++++----- 1 file changed, 128 insertions(+), 45 deletions(-) diff --git a/src/pages/[platform]/ai/concepts/models/index.mdx b/src/pages/[platform]/ai/concepts/models/index.mdx index 20b50f9bff5..7dc316b4ae6 100644 --- a/src/pages/[platform]/ai/concepts/models/index.mdx +++ b/src/pages/[platform]/ai/concepts/models/index.mdx @@ -1,4 +1,5 @@ import { getCustomStaticPath } from "@/utils/getCustomStaticPath"; +import { Table, TableBody, TableCell, TableHead, TableRow } from '@aws-amplify/ui-react'; export const meta = { title: "Models", @@ -30,12 +31,10 @@ export function getStaticProps(context) { A foundation model is a large, general-purpose machine learning model that has been pre-trained on a vast amount of data. These models are trained in an unsupervised or self-supervised manner, meaning they learn patterns and representations from the unlabeled training data without being given specific instructions or labels. -Foundation models are useful because they are general-purpose and you don't need to train the models yourself, but are powerful enough to take on a range of applications. +Foundation models are useful because they are general-purpose and you don't need to train the models yourself, but are powerful enough to take on a range of applications. Foundation Models, which Large Language Models are a part of, are inherently stateless. They take input in the form of text or images and generate text or images. They are also inherently non-deterministic. Providing the same input can generate different output. - - ## Getting model access Before you can invoke a foundation model on Bedrock you will need to [request access to the models in the AWS console](https://console.aws.amazon.com/bedrock/home#/modelaccess). @@ -44,51 +43,137 @@ Be sure to check the region you are building your Amplify app in! ## Pricing and Limits -Each foundation model in Amazon Bedrock has its own pricing and throughput limits for on-demand use. On-demand use is serverless, you don't need to provision any AWS resources to use and you only pay for what you use. The Amplify AI kit uses on-demand use for Bedrock. +Each foundation model in Amazon Bedrock has its own pricing and throughput limits for on-demand use. On-demand use is serverless, you don't need to provision any AWS resources to use and you only pay for what you use. The Amplify AI kit uses on-demand use for Bedrock. -The cost for using foundation models is calculated by token usage. A token in generative AI refers to chunks of data that were sent as input and how much data was generated. A token is roughly equal to a word, but depends on the model being used. Each foundation model in Bedrock has its own pricing based on input and output tokens used. +The cost for using foundation models is calculated by token usage. A token in generative AI refers to chunks of data that were sent as input and how much data was generated. A token is roughly equal to a word, but depends on the model being used. Each foundation model in Bedrock has its own pricing based on input and output tokens used. When you use the Amplify AI Kit, inference requests are charged to your AWS account based on Bedrock pricing. There is no Amplify markup, you are just using AWS resources in your own account. Always refer to [Bedrock pricing](https://aws.amazon.com/bedrock/pricing/) for the most up-to-date information on running generative AI with Amplify AI Kit. + + Your Amplify project must be deployed to a region where the foundation model you specify is available. See [Bedrock model support](https://docs.aws.amazon.com/bedrock/latest/userguide/models-supported.html) for the supported regions per model. + ## Supported Providers and Models -The Amplify AI Kit uses Bedrock's [Converse API](https://docs.aws.amazon.com/bedrock/latest/userguide/conversation-inference.html) to leverage a unified API across models. Most models have different structures to how they best work with input and how they format their output. For example, ... - -### AI21 Labs -* Jamba 1.5 Large -* Jamba 1.5 Mini -[Bedrock documentation about AI21 models](https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-ai21.html) - -### Anthropic -* Claude 3 Haiku -* Claude 3.5 Haiku -* Claude 3 Sonnet -* Claude 3 Opus -* Claude 3.5 Sonnet -* Claude 3.5 Sonnet v2 -[Bedrock documentation about Anthropic models](https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-claude.html) - -### Cohere -* Command R -* Command R+ -[Bedrock documentation about Cohere models](https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-cohere.html) - -### Meta Llama -* Llama 3.1 -[Bedrock documentation about Meta Llama models](https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-meta.html) - -### Mistral AI -* Large -* Large 2 -[Bedrock documentation about Mistral AI models](https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-mistral.html) - - -The Amplify AI Kit makes use of ["tools"](/[platform]/ai/concepts/tools) for both generation and conversation routes. [The models it supports must support tool use in the Converse API](https://docs.aws.amazon.com/bedrock/latest/userguide/conversation-inference-supported-models-features.html). - -Using the Converse API makes it easy to swap different models without having to drastically change how you interact with them. +The Amplify AI Kit uses Bedrock's [Converse API](https://docs.aws.amazon.com/bedrock/latest/userguide/conversation-inference.html) to leverage a unified API across models. + + + + + Provider + Model + Conversation + Generation + + + + + AI21 Labs + Jurassic-2 Large + + + + + AI21 Labs + Jurassic-2 Mini + + + + + Amazon + Amazon Nova Pro + + + + + Amazon + Amazon Nova Lite + + + + + Amazon + Amazon Nova Micro + + + + + Anthropic + Claude 3 Haiku + + + + + Anthropic + Claude 3.5 Haiku + + + + + Anthropic + Claude 3 Sonnet + + + + + Anthropic + Claude 3.5 Sonnet + + + + + Anthropic + Claude 3.5 Sonnet v2 + + + + + Anthropic + Claude 3 Opus + + + + + Cohere + Command R + + + + + Cohere + Command R+ + + + + + Meta + Llama 3.1 + + + + + Mistral AI + Large + + + + + Mistral AI + Large 2 + + + + +
+ +Amplify AI Kit makes use of ["tools"](/[platform]/ai/concepts/tools) for both generation and conversation routes. [The models used must support tool use in the Converse API](https://docs.aws.amazon.com/bedrock/latest/userguide/conversation-inference-supported-models-features.html). + +Most models have different structures to how they best work with input and how they format their output. Using the Converse API makes it easy to swap different models without having to drastically change how you interact with them. ## Choosing a model @@ -100,17 +185,17 @@ Each model has its own context window size. The context window is how much infor ### Latency -Smaller models tend to have a lower latency than larger models, but can also sometimes be less powerful. +Smaller models tend to have a lower latency than larger models, but can also sometimes be less powerful. ### Cost -Each model has its own price and throughput. +Each model has its own price and throughput. ### Use-case fit -Some models are trained to be better at certain tasks or with certain languages. +Some models are trained to be better at certain tasks or with certain languages. -Choosing the right model for your use case is balancing latency, cost, and performance. +Choosing the right model for your use case is balancing latency, cost, and performance. ## Using different models @@ -136,5 +221,3 @@ const schema = a.schema({ }) }) ``` - - From af8d6f3105eaac18e39fa3909816fd914ebce6c9 Mon Sep 17 00:00:00 2001 From: Nikhil Swaminathan <2429410+swaminator@users.noreply.github.com> Date: Mon, 9 Dec 2024 10:29:06 -0800 Subject: [PATCH 09/25] fix code snippet (#8146) --- .../auth/concepts/external-identity-providers/index.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/[platform]/build-a-backend/auth/concepts/external-identity-providers/index.mdx b/src/pages/[platform]/build-a-backend/auth/concepts/external-identity-providers/index.mdx index 4090850b93e..4dcd1a58e68 100644 --- a/src/pages/[platform]/build-a-backend/auth/concepts/external-identity-providers/index.mdx +++ b/src/pages/[platform]/build-a-backend/auth/concepts/external-identity-providers/index.mdx @@ -225,7 +225,7 @@ import { defineAuth } from '@aws-amplify/backend'; export const auth = defineAuth({ loginWith: { - externalAuthProviders: { + externalProviders: { loginWithAmazon: { clientId: secret('LOGINWITHAMAZON_CLIENT_ID'), clientSecret: secret('LOGINWITHAMAZON_CLIENT_SECRET'), From dce8c9adc559e3ef2350fde945412684a8687731 Mon Sep 17 00:00:00 2001 From: James Jarvis Date: Mon, 9 Dec 2024 11:54:14 -0800 Subject: [PATCH 10/25] docs(js): expand passwordless examples for multistep sign in, autosign in, and switching flows (#8138) * add additional detail to switching auth flows page * expand autosignin examples * clarify language * fix wording * add passwordless steps to multistep sign in page * tweak language * Update src/pages/[platform]/build-a-backend/auth/connect-your-frontend/switching-authentication-flows/index.mdx Co-authored-by: Parker Scanlon <69879391+scanlonp@users.noreply.github.com> --------- Co-authored-by: Parker Scanlon <69879391+scanlonp@users.noreply.github.com> --- .../multi-step-sign-in/index.mdx | 87 +++++++++++++++++++ .../connect-your-frontend/sign-up/index.mdx | 39 +++++++-- .../switching-authentication-flows/index.mdx | 49 +++++++---- 3 files changed, 153 insertions(+), 22 deletions(-) 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 d19dc10329e..97c53c2d0b6 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 @@ -80,6 +80,21 @@ if (nextStep.signInStep === 'CONTINUE_SIGN_IN_WITH_TOTP_SETUP') { }); } +if (nextStep.signInStep === 'CONFIRM_SIGN_IN_WITH_PASSWORD') { + // collect password from user + await confirmSignIn({ + challengeResponse: 'hunter2', + }); +} + +if (nextStep.signInStep === 'CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION') { + // present nextStep.availableChallenges to user + // collect user selection + await confirmSignIn({ + challengeResponse: 'SMS_OTP', // or 'EMAIL_OTP', 'WEB_AUTHN', 'PASSWORD', 'PASSWORD_SRP' + }); +} + if (nextStep.signInStep === 'CONFIRM_SIGN_IN_WITH_CUSTOM_CHALLENGE') { // collect custom challenge answer from user await confirmSignIn({ @@ -361,6 +376,78 @@ async function handleMfaSelection(mfaType: MfaType) { ``` +## Confirm sign-in with Password + +If the next step is `CONFIRM_SIGN_IN_WITH_PASSWORD`, the user must provide their password as the first factor authentication method. To handle this step, your implementation should prompt the user to enter their password. After the user enters the password, pass the value to the `confirmSignIn` API. + +```ts +import { type SignInOutput, confirmSignIn } from '@aws-amplify/auth'; + +async function handleSignInResult(result: SignInOutput) { + switch (result.nextStep.signInStep) { + case 'CONFIRM_SIGN_IN_WITH_PASSWORD': { + // Prompt user to enter their password + console.log(`Please enter your password.`); + break; + } + } +} + +async function confirmWithPassword(password: string) { + const result = await confirmSignIn({ challengeResponse: password }); + + return handleSignInResult(result); +} +``` + +## Continue sign-in with First Factor Selection + +If the next step is `CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION`, the user must select a first factor method for authentication. After the user selects an option, your implementation should pass the selected method to the `confirmSignIn` API. + +The first factor types which are currently supported by Amplify Auth are: +- `SMS_OTP` +- `EMAIL_OTP` +- `WEB_AUTHN` +- `PASSWORD` +- `PASSWORD_SRP` + +Depending on your configuration and what factors the user has previously setup, not all options may be available. Only the available options will be presented in `availableChallenges` for selection. + +Once Amplify receives the user's selection via the `confirmSignIn` API, you can expect to handle a follow up `nextStep` corresponding with the first factor type selected: +- If `SMS_OTP` is selected, `CONFIRM_SIGN_IN_WITH_SMS_CODE` will be the next step. +- If `EMAIL_OTP` is selected, `CONFIRM_SIGN_IN_WITH_EMAIL_CODE` will be the next step. +- If `PASSWORD` or `PASSWORD_SRP` is selected, `CONFIRM_SIGN_IN_WITH_PASSWORD` will be the next step. +- If `WEB_AUTHN` is selected, Amplify Auth will initiate the authentication ceremony on the user's device. If successful, the next step will be `DONE`. + + +```ts +import { type SignInOutput, confirmSignIn } from '@aws-amplify/auth'; + +async function handleSignInResult(result: SignInOutput) { + switch (result.nextStep.signInStep) { + case 'CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION': { + const { availableChallenges } = result.nextStep; + // Present available first factor options to user + // Prompt for selection + console.log( + `There are multiple first factor options available for sign in.`, + ); + console.log( + `Select a first factor type from the availableChallenges list.`, + ); + break; + } + } +} + +async function handleFirstFactorSelection(firstFactorType: string) { + const result = await confirmSignIn({ challengeResponse: firstFactorType }); + + return handleSignInResult(result); +} + +``` + ## Confirm sign-in with custom challenge If the next step is `CONFIRM_SIGN_IN_WITH_CUSTOM_CHALLENGE`, Amplify Auth is awaiting completion of a custom authentication challenge. The challenge is based on the AWS Lambda trigger you configured as part of a custom sign in flow. 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 f06dcbdb8bc..f5c72df2d6a 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 @@ -543,7 +543,7 @@ Your application's users can also sign up using passwordless methods. To learn m ```typescript // Sign up using a phone number const { nextStep: signUpNextStep } = await signUp({ - username: 'james', + username: 'hello', options: { userAttributes: { phone_number: '+15555551234', @@ -566,7 +566,7 @@ if (signUpNextStep.signUpStep === 'CONFIRM_SIGN_UP') { // Confirm sign up with the OTP received const { nextStep: confirmSignUpNextStep } = await confirmSignUp({ - username: 'james', + username: 'hello', confirmationCode: '123456', }); @@ -852,10 +852,10 @@ func confirmSignUp(for username: String, with confirmationCode: String) -> AnyCa ```typescript // Sign up using an email address const { nextStep: signUpNextStep } = await signUp({ - username: 'james', + username: 'hello', options: { userAttributes: { - email: 'james@example.com', + email: 'hello@example.com', }, }, }); @@ -875,7 +875,7 @@ if (signUpNextStep.signUpStep === 'CONFIRM_SIGN_UP') { // Confirm sign up with the OTP received const { nextStep: confirmSignUpNextStep } = await confirmSignUp({ - username: 'james', + username: 'hello', confirmationCode: '123456', }); @@ -1158,19 +1158,44 @@ func confirmSignUp(for username: String, with confirmationCode: String) -> AnyCa ```typescript -// Confirm sign up with the OTP received and auto sign in +// Call `signUp` API with `USER_AUTH` as the authentication flow type for `autoSignIn` +const { nextStep: signUpNextStep } = await signUp({ + username: 'hello', + options: { + userAttributes: { + email: 'hello@example.com', + phone_number: '+15555551234', + }, + autoSignIn: { + authFlowType: 'USER_AUTH', + }, + }, +}); + +if (signUpNextStep.signUpStep === 'CONFIRM_SIGN_UP') { + console.log( + `Code Delivery Medium: ${signUpNextStep.codeDeliveryDetails.deliveryMedium}`, + ); + console.log( + `Code Delivery Destination: ${signUpNextStep.codeDeliveryDetails.destination}`, + ); +} + +// Call `confirmSignUp` API with the OTP received const { nextStep: confirmSignUpNextStep } = await confirmSignUp({ - username: 'james', + username: 'hello', confirmationCode: '123456', }); if (confirmSignUpNextStep.signUpStep === 'COMPLETE_AUTO_SIGN_IN') { + // Call `autoSignIn` API to complete the flow const { nextStep } = await autoSignIn(); if (nextStep.signInStep === 'DONE') { console.log('Successfully signed in.'); } } + ``` 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 f0bcbe79167..d08f0c15f9a 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 @@ -148,18 +148,9 @@ await signIn({ ## 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`. +The `USER_AUTH` sign in flow supports the following methods as first factors for 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. +If the desired first factor is known when authentication is initiated, it can be passed to the `signIn` API as the `preferredChallenge` to initiate the corresponding authentication flow. ```ts // PASSWORD_SRP / PASSWORD @@ -176,9 +167,9 @@ const { nextStep } = await signIn({ // WEB_AUTHN / EMAIL_OTP / SMS_OTP // sign in with preferred passwordless challenge -// no user input required at this step +// no additional user input required at this step const { nextStep } = await signIn({ - username: "passwordless@mycompany.com", + username: "hello@example.com", options: { authFlowType: "USER_AUTH", preferredChallenge: "WEB_AUTHN" // or "EMAIL_OTP" or "SMS_OTP" @@ -186,9 +177,37 @@ const { nextStep } = await signIn({ }); ``` -If the desired first factor is not known, the flow will continue to select an available first factor. +If the desired first factor is not known or you would like to provide users with the available options, `preferredChallenge` can be omitted from the initial `signIn` API call. + +This allows you to discover which authentication first factors are available for a user via the `CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION` step. You can then present the available options to the user and use the `confirmSignIn` API to respond with the user's selection. + +```ts +const { nextStep: signInNextStep } = await signIn({ + username: '+15551234567', + options: { + authFlowType: 'USER_AUTH', + }, +}); + +if ( + signInNextStep.signInStep === 'CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION' +) { + // present user with list of available challenges + console.log(`Available Challenges: ${signInNextStep.availableChallenges}`); + + // respond with user selection using `confirmSignIn` API + const { nextStep: nextConfirmSignInStep } = await confirmSignIn({ + challengeResponse: 'SMS_OTP', // or 'EMAIL_OTP', 'WEB_AUTHN', 'PASSWORD', 'PASSWORD_SRP' + }); +} + +``` +Also, note that if the `preferredChallenge` passed to the initial `signIn` API call is unavailable for the user, Amplify will also respond with the `CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION` next step. + -> 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/) + +For more information about determining a first factor, and signing in with passwordless authentication factors, please visit the [Passwordless](/[platform]/build-a-backend/auth/concepts/passwordless/) concepts page. + ## USER_PASSWORD_AUTH flow From 2f1a163537406e2e8ccf8390a63042c96b4db219 Mon Sep 17 00:00:00 2001 From: Kethan sai Date: Mon, 9 Dec 2024 16:42:11 -0500 Subject: [PATCH 11/25] adds resourceGroupName to function docs (#8140) * add resourceGroupName --- .../functions/configure-functions/index.mdx | 11 +++++++++++ .../functions/examples/add-user-to-group/index.mdx | 3 ++- .../functions/examples/custom-auth-flows/index.mdx | 3 +++ .../functions/examples/custom-message/index.mdx | 1 + .../functions/examples/dynamo-db-stream/index.mdx | 1 + .../examples/google-recaptcha-challenge/index.mdx | 3 +++ .../functions/examples/override-token/index.mdx | 1 + .../examples/s3-upload-confirmation/index.mdx | 1 + .../examples/user-attribute-validation/index.mdx | 3 ++- 9 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/pages/[platform]/build-a-backend/functions/configure-functions/index.mdx b/src/pages/[platform]/build-a-backend/functions/configure-functions/index.mdx index 5a79e735d11..8a6d6bbfd47 100644 --- a/src/pages/[platform]/build-a-backend/functions/configure-functions/index.mdx +++ b/src/pages/[platform]/build-a-backend/functions/configure-functions/index.mdx @@ -90,3 +90,14 @@ export const myDemoFunction = defineFunction({ entry: './path/to/handler.ts' // this path should either be absolute or relative to the current file }); ``` + +## `resourceGroupName` + +By default, functions are grouped together in a resource group named `function`. You can override this to group related function with other Amplify resources like `auth`, `data`, `storage`, or separate them into your own custom group. +This is typically useful when you have resources that depend on each other and you want to group them together. + +```ts title="amplify/functions/my-demo-function/resource.ts" +export const myDemoFunction = defineFunction({ + resourceGroupName: 'data' +}); +``` diff --git a/src/pages/[platform]/build-a-backend/functions/examples/add-user-to-group/index.mdx b/src/pages/[platform]/build-a-backend/functions/examples/add-user-to-group/index.mdx index d566360db1a..35d8864c1c5 100644 --- a/src/pages/[platform]/build-a-backend/functions/examples/add-user-to-group/index.mdx +++ b/src/pages/[platform]/build-a-backend/functions/examples/add-user-to-group/index.mdx @@ -53,7 +53,8 @@ export const postConfirmation = defineFunction({ // optionally define an environment variable for your group name environment: { GROUP_NAME: 'EVERYONE' - } + }, + resourceGroupName: 'auth' }); ``` diff --git a/src/pages/[platform]/build-a-backend/functions/examples/custom-auth-flows/index.mdx b/src/pages/[platform]/build-a-backend/functions/examples/custom-auth-flows/index.mdx index 7b62b66f3d8..8cd6dbe2a37 100644 --- a/src/pages/[platform]/build-a-backend/functions/examples/custom-auth-flows/index.mdx +++ b/src/pages/[platform]/build-a-backend/functions/examples/custom-auth-flows/index.mdx @@ -53,6 +53,7 @@ import { defineFunction } from "@aws-amplify/backend" export const createAuthChallenge = defineFunction({ name: "create-auth-challenge", + resourceGroupName: 'auth' }) ``` @@ -89,6 +90,7 @@ import { defineFunction } from "@aws-amplify/backend" export const defineAuthChallenge = defineFunction({ name: "define-auth-challenge", + resourceGroupName: 'auth' }) ``` @@ -183,6 +185,7 @@ import { defineFunction, secret } from "@aws-amplify/backend" export const verifyAuthChallengeResponse = defineFunction({ name: "verify-auth-challenge-response", + resourceGroupName: 'auth' }) ``` diff --git a/src/pages/[platform]/build-a-backend/functions/examples/custom-message/index.mdx b/src/pages/[platform]/build-a-backend/functions/examples/custom-message/index.mdx index e7141784a5a..a1f562126f2 100644 --- a/src/pages/[platform]/build-a-backend/functions/examples/custom-message/index.mdx +++ b/src/pages/[platform]/build-a-backend/functions/examples/custom-message/index.mdx @@ -45,6 +45,7 @@ import { defineFunction } from '@aws-amplify/backend'; export const customMessage = defineFunction({ name: "custom-message", + resourceGroupName: 'auth' }); ``` diff --git a/src/pages/[platform]/build-a-backend/functions/examples/dynamo-db-stream/index.mdx b/src/pages/[platform]/build-a-backend/functions/examples/dynamo-db-stream/index.mdx index 97ece1d1e6f..0549e0d6ef2 100644 --- a/src/pages/[platform]/build-a-backend/functions/examples/dynamo-db-stream/index.mdx +++ b/src/pages/[platform]/build-a-backend/functions/examples/dynamo-db-stream/index.mdx @@ -46,6 +46,7 @@ import { defineFunction } from "@aws-amplify/backend"; export const myDynamoDBFunction = defineFunction({ name: "dynamoDB-function", + resourceGroupName: "data", }); ``` diff --git a/src/pages/[platform]/build-a-backend/functions/examples/google-recaptcha-challenge/index.mdx b/src/pages/[platform]/build-a-backend/functions/examples/google-recaptcha-challenge/index.mdx index 3f6f0699b71..737851a7e9e 100644 --- a/src/pages/[platform]/build-a-backend/functions/examples/google-recaptcha-challenge/index.mdx +++ b/src/pages/[platform]/build-a-backend/functions/examples/google-recaptcha-challenge/index.mdx @@ -44,6 +44,7 @@ import { defineFunction } from "@aws-amplify/backend" export const createAuthChallenge = defineFunction({ name: "create-auth-challenge", + resourceGroupName: 'auth' }) ``` @@ -79,6 +80,7 @@ import { defineFunction } from "@aws-amplify/backend" export const defineAuthChallenge = defineFunction({ name: "define-auth-challenge", + resourceGroupName: 'auth' }) ``` @@ -144,6 +146,7 @@ export const verifyAuthChallengeResponse = defineFunction({ environment: { GOOGLE_RECAPTCHA_SECRET_KEY: secret("GOOGLE_RECAPTCHA_SECRET_KEY"), }, + resourceGroupName: 'auth' }) ``` diff --git a/src/pages/[platform]/build-a-backend/functions/examples/override-token/index.mdx b/src/pages/[platform]/build-a-backend/functions/examples/override-token/index.mdx index e0ecd1f59eb..0a49abca08a 100644 --- a/src/pages/[platform]/build-a-backend/functions/examples/override-token/index.mdx +++ b/src/pages/[platform]/build-a-backend/functions/examples/override-token/index.mdx @@ -45,6 +45,7 @@ import { defineFunction } from '@aws-amplify/backend'; export const preTokenGeneration = defineFunction({ name: 'pre-token-generation', + resourceGroupName: 'auth' }); ``` diff --git a/src/pages/[platform]/build-a-backend/functions/examples/s3-upload-confirmation/index.mdx b/src/pages/[platform]/build-a-backend/functions/examples/s3-upload-confirmation/index.mdx index fb3a5ba26ee..ccdf90db6c4 100644 --- a/src/pages/[platform]/build-a-backend/functions/examples/s3-upload-confirmation/index.mdx +++ b/src/pages/[platform]/build-a-backend/functions/examples/s3-upload-confirmation/index.mdx @@ -47,6 +47,7 @@ export const storage = defineStorage({ triggers: { onUpload: defineFunction({ entry: './on-upload-handler.ts' + resourceGroupName: 'storage', }) } }); diff --git a/src/pages/[platform]/build-a-backend/functions/examples/user-attribute-validation/index.mdx b/src/pages/[platform]/build-a-backend/functions/examples/user-attribute-validation/index.mdx index b92bee59090..b284fac20bb 100644 --- a/src/pages/[platform]/build-a-backend/functions/examples/user-attribute-validation/index.mdx +++ b/src/pages/[platform]/build-a-backend/functions/examples/user-attribute-validation/index.mdx @@ -37,7 +37,8 @@ To get started, create a new directory and a resource file, `amplify/auth/pre-si import { defineFunction } from '@aws-amplify/backend'; export const preSignUp = defineFunction({ - name: "pre-sign-up" + name: "pre-sign-up", + resourceGroupName: 'auth' }); ``` From b362e1b66dcb5af82d76b3000d53207509b730ca Mon Sep 17 00:00:00 2001 From: Ivan Artemiev <29709626+iartemiev@users.noreply.github.com> Date: Mon, 9 Dec 2024 16:58:19 -0500 Subject: [PATCH 12/25] feat: add instructions for adding event api to backend (#8155) * feat: add instructions for adding event api to backend * Apply suggestions from code review Co-authored-by: josef --------- Co-authored-by: josef --- .../data/connect-event-api/index.mdx | 172 +++++++++++++++++- 1 file changed, 168 insertions(+), 4 deletions(-) diff --git a/src/pages/[platform]/build-a-backend/data/connect-event-api/index.mdx b/src/pages/[platform]/build-a-backend/data/connect-event-api/index.mdx index 777d84dc8c5..3f79f58a3bc 100644 --- a/src/pages/[platform]/build-a-backend/data/connect-event-api/index.mdx +++ b/src/pages/[platform]/build-a-backend/data/connect-event-api/index.mdx @@ -89,8 +89,8 @@ export default function App() { <>
    - {myEvents.map((event, index) => ( -
  • {JSON.stringify(event)}
  • + {myEvents.map((data) => ( +
  • {JSON.stringify(data.event)}
  • ))}
@@ -98,6 +98,170 @@ export default function App() { } ``` -## Connect to an Event API with an existing Amplify backend +## Add an Event API to an existing Amplify backend -Coming Soon +This guide walks through how you can add an Event API to an existing Amplify backend. We'll be using Cognito User Pools for authenticating with Event API from our frontend application. Any signed in user will be able to subscribe to the Event API and publish events. + +Before you begin, you will need: + +- An existing Amplify backend (see [Quickstart](/[platform]/start/quickstart/)) +- Latest versions of `@aws-amplify/backend` and `@aws-amplify/backend-cli` (`npm add @aws-amplify/backend@latest @aws-amplify/backend-cli@latest`) + +### Update Backend Definition + +First, we'll add a new Event API to our backend definition. + +```ts title="amplify/backend.ts" +import { defineBackend } from '@aws-amplify/backend'; +import { auth } from './auth/resource'; +// highlight-start +// import CDK resources: +import { + CfnApi, + CfnChannelNamespace, + AuthorizationType, +} from 'aws-cdk-lib/aws-appsync'; +import { Policy, PolicyStatement } from 'aws-cdk-lib/aws-iam'; +// highlight-end + +const backend = defineBackend({ + auth, +}); + +// highlight-start +// create a new stack for our Event API resources: +const customResources = backend.createStack('custom-resources'); + +// add a new Event API to the stack: +const cfnEventAPI = new CfnApi(customResources, 'CfnEventAPI', { + name: 'my-event-api', + eventConfig: { + authProviders: [ + { + authType: AuthorizationType.USER_POOL, + cognitoConfig: { + awsRegion: customResources.region, + // configure Event API to use the Cognito User Pool provisioned by Amplify: + userPoolId: backend.auth.resources.userPool.userPoolId, + }, + }, + ], + // configure the User Pool as the auth provider for Connect, Publish, and Subscribe operations: + connectionAuthModes: [{ authType: AuthorizationType.USER_POOL }], + defaultPublishAuthModes: [{ authType: AuthorizationType.USER_POOL }], + defaultSubscribeAuthModes: [{ authType: AuthorizationType.USER_POOL }], + }, +}); + +// create a default namespace for our Event API: +const namespace = new CfnChannelNamespace( + customResources, + 'CfnEventAPINamespace', + { + apiId: cfnEventAPI.attrApiId, + name: 'default', + } +); + +// attach a policy to the authenticated user role in our User Pool to grant access to the Event API: +backend.auth.resources.authenticatedUserIamRole.attachInlinePolicy( + new Policy(customResources, 'AppSyncEventPolicy', { + statements: [ + new PolicyStatement({ + actions: [ + 'appsync:EventConnect', + 'appsync:EventSubscribe', + 'appsync:EventPublish', + ], + resources: [`${cfnEventAPI.attrApiArn}/*`, `${cfnEventAPI.attrApiArn}`], + }), + ], + }) +); + +// finally, add the Event API configuration to amplify_outputs: +backend.addOutput({ + custom: { + events: { + url: `https://${cfnEventAPI.getAtt('Dns.Http').toString()}/event`, + aws_region: customResources.region, + default_authorization_type: AuthorizationType.USER_POOL, + }, + }, +}); +// highlight-end +``` + +### Deploy Backend + +To test your changes, deploy your Amplify Sandbox. + +```bash title="Terminal" showLineNumbers={false} +npx ampx sandbox +``` + +### Connect your frontend application + +After the sandbox deploys, connect your frontend application to the Event API. We'll be using the [Amplify Authenticator component](https://ui.docs.amplify.aws/react/connected-components/authenticator) to sign in to our Cognito User Pool. + +If you don't already have the Authenticator installed, you can install it by running `npm add @aws-amplify/ui-react`. + +```tsx title="src/App.tsx" +import { useEffect, useState } from 'react'; +import { Amplify } from 'aws-amplify'; +import { events, type EventsChannel } from 'aws-amplify/data'; +import { Authenticator } from '@aws-amplify/ui-react'; +import '@aws-amplify/ui-react/styles.css'; +import outputs from '../amplify_outputs.json'; + +Amplify.configure(outputs); + +export default function App() { + const [myEvents, setMyEvents] = useState[]>([]); + + useEffect(() => { + let channel: EventsChannel; + + const connectAndSubscribe = async () => { + channel = await events.connect('default/channel'); + + channel.subscribe({ + next: (data) => { + console.log('received', data); + setMyEvents((prev) => [data, ...prev]); + }, + error: (err) => console.error('error', err), + }); + }; + + connectAndSubscribe(); + + return () => channel && channel.close(); + }, []); + + async function publishEvent() { + await events.post('default/channel', { some: 'data' }); + } + + return ( + + {({ signOut, user }) => ( + <> +
+

Welcome, {user.username}

+ +
+
+ +
    + {myEvents.map((data) => ( +
  • {JSON.stringify(data.event)}
  • + ))} +
+
+ + )} +
+ ); +} +``` From 0dfff0067b6188d769e1133916f237c5a924c2e7 Mon Sep 17 00:00:00 2001 From: "Aaron S." <94858815+stocaaro@users.noreply.github.com> Date: Mon, 9 Dec 2024 16:00:29 -0600 Subject: [PATCH 13/25] feat: Fix examples to use lambda data client (#8096) * feat: Fix examples to use lambda data client * fix: Schema import pattern * Update src/pages/[platform]/build-a-backend/data/customize-authz/grant-lambda-function-access-to-api/index.mdx Co-authored-by: josef * Add warning after data client handler examples * Update src/pages/[platform]/build-a-backend/data/customize-authz/grant-lambda-function-access-to-api/index.mdx Co-authored-by: josef * Update src/pages/[platform]/build-a-backend/data/customize-authz/grant-lambda-function-access-to-api/index.mdx Co-authored-by: josef * Update src/pages/[platform]/build-a-backend/data/customize-authz/grant-lambda-function-access-to-api/index.mdx Co-authored-by: josef * Apply suggestions from code review Co-authored-by: josef --------- Co-authored-by: josef --- .../index.mdx | 105 ++++++------------ .../create-user-profile-record/index.mdx | 63 +++-------- 2 files changed, 47 insertions(+), 121 deletions(-) diff --git a/src/pages/[platform]/build-a-backend/data/customize-authz/grant-lambda-function-access-to-api/index.mdx b/src/pages/[platform]/build-a-backend/data/customize-authz/grant-lambda-function-access-to-api/index.mdx index e0d025f2572..04da21c3ebf 100644 --- a/src/pages/[platform]/build-a-backend/data/customize-authz/grant-lambda-function-access-to-api/index.mdx +++ b/src/pages/[platform]/build-a-backend/data/customize-authz/grant-lambda-function-access-to-api/index.mdx @@ -35,19 +35,16 @@ Function access to `defineData` can be configured using an authorization rule on import { a, defineData, - defineFunction, type ClientSchema } from '@aws-amplify/backend'; - -const functionWithDataAccess = defineFunction({ - entry: '../functions/data-access.ts' -}); +import { functionWithDataAccess } from '../function/data-access/resource'; const schema = a .schema({ Todo: a.model({ name: a.string(), - description: a.string() + description: a.string(), + isDone: a.boolean() }) }) // highlight-next-line @@ -60,14 +57,25 @@ export const data = defineData({ }); ``` +Create a new directory and a resource file, `amplify/functions/data-access/resource.ts`. Then, define the Function with `defineFunction`: + +```ts title="amplify/functions/data-access/resource.ts" +import { defineFunction } from '@aws-amplify/backend'; + +export const functionWithDataAccess = defineFunction({ + name: 'data-access', +}); +``` + The object returned from `defineFunction` can be passed directly to `allow.resource()` in the schema authorization rules. This will grant the function the ability to execute Query, Mutation, and Subscription operations against the GraphQL API. Use the `.to()` method to narrow down access to one or more operations. -```ts +```ts title="amplify/data/resource.ts" const schema = a .schema({ Todo: a.model({ name: a.string(), - description: a.string() + description: a.string(), + isDone: a.boolean() }) }) // highlight-start @@ -77,8 +85,6 @@ const schema = a // highlight-end ``` -When configuring function access, the function will be provided the API endpoint as an environment variable named `_GRAPHQL_ENDPOINT`, where `defineDataName` is transformed to SCREAMING_SNAKE_CASE. The default name is `AMPLIFY_DATA_GRAPHQL_ENDPOINT` unless you have specified a different name in `defineData`. - Function access can only be configured on the schema object. It cannot be configured on individual models or fields. @@ -89,64 +95,27 @@ Function access can only be configured on the schema object. It cannot be config In the handler file for your function, configure the Amplify data client -```ts title="amplify/functions/data-access.ts" +```ts title="amplify/functions/data-access/handler.ts" +import type { Handler } from 'aws-lambda'; +import type { Schema } from '../../data/resource'; import { Amplify } from 'aws-amplify'; import { generateClient } from 'aws-amplify/data'; -import { Schema } from '../data/resource'; +import { getAmplifyDataClientConfig } from '@aws-amplify/backend/function/runtime'; import { env } from '$amplify/env/'; // replace with your function name +const { resourceConfig, libraryOptions } = await getAmplifyDataClientConfig(env); -Amplify.configure( - { - API: { - GraphQL: { - endpoint: env._GRAPHQL_ENDPOINT, // replace with your defineData name - region: env.AWS_REGION, - defaultAuthMode: 'identityPool' - } - } - }, - { - Auth: { - credentialsProvider: { - getCredentialsAndIdentityId: async () => ({ - credentials: { - accessKeyId: env.AWS_ACCESS_KEY_ID, - secretAccessKey: env.AWS_SECRET_ACCESS_KEY, - sessionToken: env.AWS_SESSION_TOKEN, - }, - }), - clearCredentialsAndIdentityId: () => { - /* noop */ - }, - }, - }, - } -); - -const dataClient = generateClient(); +Amplify.configure(resourceConfig, libraryOptions); + +const client = generateClient(); export const handler = async (event) => { // your function code goes here } ``` -Use the command below to generate GraphQL client code to call your data backend. - - -**Note**: We are working on bringing the end-to-end typed experience to connect to your data from within function resources without needing this step. If you'd like to provide feedback the experience or have early access, join our [Discord community](https://discord.gg/amplify). - - - -```sh title="Terminal" showLineNumbers={false} -npx ampx generate graphql-client-code --out /graphql -``` - - - -**Note:** Whenever you update your data model, you will need to run the command above again. - +When configuring Amplify with `getAmplifyDataClientConfig`, your function consumes schema information from an S3 bucket created during backend deployment with grants for the access your function need to use it. Any changes to this bucket outside of backend deployment may break your function. Once you have generated the client code, update the function to access the data. The following code creates a todo and then lists all todos. @@ -154,21 +123,15 @@ Once you have generated the client code, update the function to access the data. ```ts title="amplify/functions/data-access.ts" const client = generateClient(); -export const handler = async (event) => { - await client.graphql({ - query: createTodo, - variables: { - input: { - name: "My first todo", - description: "This is my first todo", - }, - }, - }); - - - await client.graphql({ - query: listTodos, - }); +export const handler: Handler = async (event) => { + const { errors: createErrors, data: newTodo } = await client.models.Todo.create({ + name: "My new todo", + description: "Todo description", + isDone: false, + }) + + + const { errors: listErrors, data: todos } = await client.models.Todo.list(); return event; }; diff --git a/src/pages/[platform]/build-a-backend/functions/examples/create-user-profile-record/index.mdx b/src/pages/[platform]/build-a-backend/functions/examples/create-user-profile-record/index.mdx index 0d9dab76926..26a5dcd39fc 100644 --- a/src/pages/[platform]/build-a-backend/functions/examples/create-user-profile-record/index.mdx +++ b/src/pages/[platform]/build-a-backend/functions/examples/create-user-profile-record/index.mdx @@ -92,17 +92,6 @@ export const postConfirmation = defineFunction({ }); ``` -Run the command `npx ampx sandbox` to create the backend, then use the command below to generate GraphQL client code to call your data backend. - - -**Note**: We are working on bringing the end-to-end typed experience to connect to your data from within function resources without needing this step. If you'd like to provide feedback on the experience or want to have early access, join our [Discord community](https://discord.gg/amplify). - - - -```sh title="Terminal" showLineNumbers={false} -npx ampx generate graphql-client-code --out /graphql -``` - Then, create the corresponding handler file, `amplify/auth/post-confirmation/handler.ts`, file with the following contents: ```ts title="amplify/auth/post-confirmation/handler.ts" @@ -110,50 +99,21 @@ import type { PostConfirmationTriggerHandler } from "aws-lambda"; import { type Schema } from "../../data/resource"; import { Amplify } from "aws-amplify"; import { generateClient } from "aws-amplify/data"; +import { getAmplifyDataClientConfig } from '@aws-amplify/backend/function/runtime'; import { env } from "$amplify/env/post-confirmation"; -import { createUserProfile } from "./graphql/mutations"; - -Amplify.configure( - { - API: { - GraphQL: { - endpoint: env.AMPLIFY_DATA_GRAPHQL_ENDPOINT, - region: env.AWS_REGION, - defaultAuthMode: "iam", - }, - }, - }, - { - Auth: { - credentialsProvider: { - getCredentialsAndIdentityId: async () => ({ - credentials: { - accessKeyId: env.AWS_ACCESS_KEY_ID, - secretAccessKey: env.AWS_SECRET_ACCESS_KEY, - sessionToken: env.AWS_SESSION_TOKEN, - }, - }), - clearCredentialsAndIdentityId: () => { - /* noop */ - }, - }, - }, - } + +const { resourceConfig, libraryOptions } = await getAmplifyDataClientConfig( + env ); -const client = generateClient({ - authMode: "iam", -}); +Amplify.configure(resourceConfig, libraryOptions); + +const client = generateClient(); export const handler: PostConfirmationTriggerHandler = async (event) => { - await client.graphql({ - query: createUserProfile, - variables: { - input: { - email: event.request.userAttributes.email, - profileOwner: `${event.request.userAttributes.sub}::${event.userName}`, - }, - }, + await client.models.UserProfile.create({ + email: event.request.userAttributes.email, + profileOwner: `${event.request.userAttributes.sub}::${event.userName}`, }); return event; @@ -161,6 +121,9 @@ export const handler: PostConfirmationTriggerHandler = async (event) => { ``` + +When configuring Amplify with `getAmplifyDataClientConfig`, your function consumes schema information from an S3 bucket created during backend deployment with grants for the access your function need to use it. Any changes to this bucket outside of backend deployment may break your function. + Lastly, set the newly created Function resource on your auth resource: From df591e1da777fc75814e67ae688f34d5a625359f Mon Sep 17 00:00:00 2001 From: James Jarvis Date: Mon, 9 Dec 2024 14:45:29 -0800 Subject: [PATCH 14/25] docs(js): fix predictions setup example (#8153) * fix predictions front end configuration example * update pkg installation --- .../predictions/set-up-predictions/index.mdx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pages/[platform]/build-a-backend/add-aws-services/predictions/set-up-predictions/index.mdx b/src/pages/[platform]/build-a-backend/add-aws-services/predictions/set-up-predictions/index.mdx index 9b7642739dd..dd8f357df21 100644 --- a/src/pages/[platform]/build-a-backend/add-aws-services/predictions/set-up-predictions/index.mdx +++ b/src/pages/[platform]/build-a-backend/add-aws-services/predictions/set-up-predictions/index.mdx @@ -145,7 +145,7 @@ backend.addOutput({ To install the Amplify library to use predictions features, run the following commands in your project's root folder: ```bash title="Terminal" showLineNumbers={false} -npm add aws-amplify +npm add aws-amplify @aws-amplify/predictions ``` ## Configure the frontend @@ -153,13 +153,13 @@ npm add aws-amplify Import and load the configuration file in your app. It is recommended you add the Amplify configuration step to your app's root entry point. For example `main.ts` in React and Angular. ```ts title="src/main.ts" -import { Predictions } from "aws-amplify/predictions"; +import { Amplify } from "aws-amplify"; import outputs from "./amplify_outputs.json"; Amplify.configure(outputs); Amplify.configure({ ...Amplify.getConfig(), - Predictions: config.custom.Predictions, + Predictions: outputs.custom.Predictions, }); ``` From f9341e89c3bd838d4d99f69ff0eed61f9a621537 Mon Sep 17 00:00:00 2001 From: andreyaurelien <69720066+andreyaurelien@users.noreply.github.com> Date: Tue, 10 Dec 2024 17:24:48 +0100 Subject: [PATCH 15/25] Fix typo in description of the use of amplify_outputs.json (Angular) (#8152) --- src/pages/[platform]/start/quickstart/index.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/[platform]/start/quickstart/index.mdx b/src/pages/[platform]/start/quickstart/index.mdx index 1ed865d11c5..1255ad7d8b6 100644 --- a/src/pages/[platform]/start/quickstart/index.mdx +++ b/src/pages/[platform]/start/quickstart/index.mdx @@ -1051,7 +1051,7 @@ Now move the `amplify_outputs.json` file you downloaded above to the root of you ``` -The **amplify_outputs.json** file contains backend endpoint information, publicly-viewable API keys, authentication flow information, and more. The Amplify client library uses this outputs file to connect to your Amplify Backend. You can review how the outputs file is imported within the `main.tsx` file and then passed into the `Amplify.configure(...)` function of the Amplify client library. +The **amplify_outputs.json** file contains backend endpoint information, publicly-viewable API keys, authentication flow information, and more. The Amplify client library uses this outputs file to connect to your Amplify Backend. You can review how the outputs file is imported within the `app.component.ts` file and then passed into the `Amplify.configure(...)` function of the Amplify client library. From 97511316d0287e6a35e79daee5657c9ae1f0eedb Mon Sep 17 00:00:00 2001 From: Tiffany Yeung Date: Tue, 10 Dec 2024 14:59:29 -0800 Subject: [PATCH 16/25] fix: update Next.js docs links for serverComponentsPackages (#8156) * fix: update serverComponentExternalPackages references to latest Next.js docs links * update all property references to point to new name --- .../build-a-backend/server-side-rendering/index.mdx | 6 ++---- .../build-a-backend/server-side-rendering/nextjs/index.mdx | 6 ++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/pages/[platform]/build-a-backend/server-side-rendering/index.mdx b/src/pages/[platform]/build-a-backend/server-side-rendering/index.mdx index 1a11180a695..5e21f0c6405 100644 --- a/src/pages/[platform]/build-a-backend/server-side-rendering/index.mdx +++ b/src/pages/[platform]/build-a-backend/server-side-rendering/index.mdx @@ -244,14 +244,12 @@ You can add the following to your `next.config.js`: /** @type {import('next').NextConfig} */ const nextConfig = { // highlight-start - experimental: { - serverComponentsExternalPackages: ['@aws-crypto'], - }, + serverComponentsPackages: ['@aws-crypto'], // highlight-end }; ``` -See Next.js documentation on [`serverComponentsExternalPackages`](https://nextjs.org/docs/app/api-reference/next-config-js/serverComponentsExternalPackages) for more details. +See Next.js documentation on [`serverComponentsPackages`](https://nextjs.org/docs/app/api-reference/config/next-config-js/serverExternalPackages) for more details. ### With Next.js App Router diff --git a/src/pages/gen1/[platform]/build-a-backend/server-side-rendering/nextjs/index.mdx b/src/pages/gen1/[platform]/build-a-backend/server-side-rendering/nextjs/index.mdx index 7bf1590334a..3de9e649dc2 100644 --- a/src/pages/gen1/[platform]/build-a-backend/server-side-rendering/nextjs/index.mdx +++ b/src/pages/gen1/[platform]/build-a-backend/server-side-rendering/nextjs/index.mdx @@ -246,14 +246,12 @@ You can add the following to your `next.config.js`: /** @type {import('next').NextConfig} */ const nextConfig = { // highlight-start - experimental: { - serverComponentsExternalPackages: ['@aws-crypto'], - }, + serverComponentsPackages: ['@aws-crypto'], // highlight-end }; ``` -See Next.js documentation on [`serverComponentsExternalPackages`](https://nextjs.org/docs/app/api-reference/next-config-js/serverComponentsExternalPackages) for more details. +See Next.js documentation on [`serverComponentsPackages`](https://nextjs.org/docs/app/api-reference/config/next-config-js/serverExternalPackages) for more details. ### With Next.js App Router From 0aadfcdc26b05de1bdaf15021d5b18bf26aba10b Mon Sep 17 00:00:00 2001 From: Rene Brandel <4989523+renebrandel@users.noreply.github.com> Date: Wed, 11 Dec 2024 16:22:12 -0500 Subject: [PATCH 17/25] Update index.mdx (#8159) --- .../search-and-aggregate-queries/index.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/[platform]/build-a-backend/data/custom-business-logic/search-and-aggregate-queries/index.mdx b/src/pages/[platform]/build-a-backend/data/custom-business-logic/search-and-aggregate-queries/index.mdx index c9cd10d3deb..cd4d4a3d9fe 100644 --- a/src/pages/[platform]/build-a-backend/data/custom-business-logic/search-and-aggregate-queries/index.mdx +++ b/src/pages/[platform]/build-a-backend/data/custom-business-logic/search-and-aggregate-queries/index.mdx @@ -905,7 +905,7 @@ You can also check this in the DynamoDB console by going to the Integrations sec ## Step 4: Expose new queries on OpenSearch -### Step 4a:Add OpenSearch Datasource to backend +### Step 4a: Add OpenSearch Datasource to backend First, Add the OpenSearch data source to the data backend. Add the following code to the end of the `amplify/backend.ts` file. From 022988a54e44ba9fdedc1fd0bc31c8be6af8a40f Mon Sep 17 00:00:00 2001 From: Erik Hanchett Date: Wed, 11 Dec 2024 15:09:02 -0800 Subject: [PATCH 18/25] Added new badge and message for AI kit (#8110) Co-authored-by: dindjarinjs <187552781+dindjarinjs@users.noreply.github.com> --- mdx-components.tsx | 2 + src/components/AIBanner/AIBanner.tsx | 43 +++++++++++++++++++ .../AIBanner/__tests__/AIBanner.test.tsx | 13 ++++++ src/components/AIBanner/index.tsx | 1 + src/components/Layout/Layout.tsx | 3 ++ src/components/Menu/MenuItem.tsx | 3 +- src/directory/directory.d.ts | 5 +++ src/pages/[platform]/ai/index.mdx | 1 + src/styles/banner.scss | 22 ++++++++++ src/styles/styles.scss | 1 + 10 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 src/components/AIBanner/AIBanner.tsx create mode 100644 src/components/AIBanner/__tests__/AIBanner.test.tsx create mode 100644 src/components/AIBanner/index.tsx create mode 100644 src/styles/banner.scss diff --git a/mdx-components.tsx b/mdx-components.tsx index a587becd6b3..7ce3e403259 100644 --- a/mdx-components.tsx +++ b/mdx-components.tsx @@ -1,6 +1,7 @@ import * as React from 'react'; import type { MDXComponents } from 'mdx/types'; import ExportedImage from 'next-image-export-optimizer'; +import { AIBanner } from './src/components/AIBanner'; import InlineFilter from './src/components/InlineFilter'; import { YoutubeEmbed } from './src/components/YoutubeEmbed'; import { Accordion } from './src/components/Accordion'; @@ -64,6 +65,7 @@ export function useMDXComponents(components: MDXComponents): MDXComponents { InlineFilter, MigrationAlert, YoutubeEmbed, + AIBanner, Overview, ExternalLink, ExternalLinkButton, diff --git a/src/components/AIBanner/AIBanner.tsx b/src/components/AIBanner/AIBanner.tsx new file mode 100644 index 00000000000..fd4db18e93a --- /dev/null +++ b/src/components/AIBanner/AIBanner.tsx @@ -0,0 +1,43 @@ +import { Flex, Message, IconsProvider, Text } from '@aws-amplify/ui-react'; +import { IconStar, IconChevron } from '../Icons'; +import { Button } from '@aws-amplify/ui-react'; + +export const AIBanner: React.FC = () => { + const URL = '/react/ai/set-up-ai/'; + return ( + + } + }} + > + + + + + Amplify AI kit is now generally available + + + Create fullstack AI-powered apps with TypeScript, no prior + experience in cloud architecture or AI needed. + + + + + + + + ); +}; diff --git a/src/components/AIBanner/__tests__/AIBanner.test.tsx b/src/components/AIBanner/__tests__/AIBanner.test.tsx new file mode 100644 index 00000000000..60110d8ed8d --- /dev/null +++ b/src/components/AIBanner/__tests__/AIBanner.test.tsx @@ -0,0 +1,13 @@ +import * as React from 'react'; +import { render, screen } from '@testing-library/react'; +import { AIBanner } from '../index'; + +describe('AIBanner', () => { + it('should render the AIBanner component', async () => { + const bannerText = 'Amplify AI kit is now generally available'; + render(); + + const component = await screen.findByText(bannerText); + expect(component).toBeInTheDocument(); + }); +}); diff --git a/src/components/AIBanner/index.tsx b/src/components/AIBanner/index.tsx new file mode 100644 index 00000000000..8cf7601c7bb --- /dev/null +++ b/src/components/AIBanner/index.tsx @@ -0,0 +1 @@ +export { AIBanner } from './AIBanner'; diff --git a/src/components/Layout/Layout.tsx b/src/components/Layout/Layout.tsx index 4638d637994..b3419509203 100644 --- a/src/components/Layout/Layout.tsx +++ b/src/components/Layout/Layout.tsx @@ -29,6 +29,7 @@ import type { HeadingInterface } from '@/components/TableOfContents/TableOfConte import { Breadcrumbs } from '@/components/Breadcrumbs'; import { debounce } from '@/utils/debounce'; import '@docsearch/css'; +import { AIBanner } from '@/components/AIBanner'; import { usePathWithoutHash } from '@/utils/usePathWithoutHash'; import { NextPrevious, @@ -71,6 +72,7 @@ export const Layout = ({ const basePath = 'docs.amplify.aws'; const metaUrl = url ? url : basePath + asPathWithNoHash; const pathname = router.pathname; + const shouldShowAIBanner = asPathWithNoHash === '/'; const isGen1 = asPathWithNoHash.split('/')[1] === 'gen1'; const isContributor = asPathWithNoHash.split('/')[1] === 'contribute'; const currentGlobalNavMenuItem = isContributor ? 'Contribute' : 'Docs'; @@ -272,6 +274,7 @@ export const Layout = ({ platform={currentPlatform} /> ) : null} + {shouldShowAIBanner ? : null} {useCustomTitle ? null : ( {pageTitle} )} diff --git a/src/components/Menu/MenuItem.tsx b/src/components/Menu/MenuItem.tsx index e49cbe2510f..6478c5de608 100644 --- a/src/components/Menu/MenuItem.tsx +++ b/src/components/Menu/MenuItem.tsx @@ -1,6 +1,6 @@ import { usePathWithoutHash } from '@/utils/usePathWithoutHash'; import { ReactElement, useContext, useEffect, useState, useMemo } from 'react'; -import { Link as AmplifyUILink, Flex } from '@aws-amplify/ui-react'; +import { Link as AmplifyUILink, Flex, Badge } from '@aws-amplify/ui-react'; import { IconExternalLink, IconChevron } from '@/components/Icons'; import Link from 'next/link'; import { JS_PLATFORMS, Platform, JSPlatform } from '@/data/platforms'; @@ -200,6 +200,7 @@ export function MenuItem({ className={`menu__list-item__link__inner ${listItemLinkInnerStyle}`} > {pageNode.title} + {pageNode.isNew && New} {children && hasVisibleChildren && level !== Levels.Category && ( )} diff --git a/src/directory/directory.d.ts b/src/directory/directory.d.ts index 5d1433a162b..ad0fd85e291 100644 --- a/src/directory/directory.d.ts +++ b/src/directory/directory.d.ts @@ -57,4 +57,9 @@ export type PageNode = { * This is being used for categories like Cli - Legacy and SDK */ hideChildrenOnBase?: boolean; + + /** + * This flag indicates that the item is new and will display a "new" badge + */ + isNew?: boolean; }; diff --git a/src/pages/[platform]/ai/index.mdx b/src/pages/[platform]/ai/index.mdx index edee3b692a2..efc70d6d257 100644 --- a/src/pages/[platform]/ai/index.mdx +++ b/src/pages/[platform]/ai/index.mdx @@ -4,6 +4,7 @@ import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; export const meta = { title: 'AI kit', description: 'The quickest way for fullstack developers to build web apps with AI capabilities such as chat, conversational search, and summarization', + isNew: true, route: '/[platform]/ai', platforms: [ 'angular', diff --git a/src/styles/banner.scss b/src/styles/banner.scss new file mode 100644 index 00000000000..ab0330430b9 --- /dev/null +++ b/src/styles/banner.scss @@ -0,0 +1,22 @@ +.message-banner { + align-items: start; + /* Specificity to override Amplify UI color theme */ + &.amplify-message--info { + border: 1px solid var(--amplify-colors-purple-20); + background-color: var(--amplify-colors-purple-10); + @include darkMode { + background-color: var(--amplify-colors-purple-20); + } + } + .amplify-message__icon { + color: var(--amplify-colors-purple-80); + } + &__inner { + display: flex; + flex-direction: column; + align-items: flex-start; + } + &__heading { + font-weight: 700; + } +} diff --git a/src/styles/styles.scss b/src/styles/styles.scss index fa59b64e62c..c6958471e7b 100644 --- a/src/styles/styles.scss +++ b/src/styles/styles.scss @@ -9,6 +9,7 @@ @import './accordion.scss'; @import './block-switcher.scss'; @import './breadcrumbs.scss'; +@import './banner.scss'; @import './callout.scss'; @import './code.scss'; @import './color-switcher.scss'; From 475f4b56a9ca6909bb2ea0edeb05356d4cb8a681 Mon Sep 17 00:00:00 2001 From: Kethan sai Date: Wed, 11 Dec 2024 19:48:31 -0500 Subject: [PATCH 19/25] update module troubleshooting page with vue config (#8118) --- .../cannot-find-module-amplify-env/index.mdx | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/pages/[platform]/build-a-backend/troubleshooting/cannot-find-module-amplify-env/index.mdx b/src/pages/[platform]/build-a-backend/troubleshooting/cannot-find-module-amplify-env/index.mdx index 3124afdacd0..31c5b35a605 100644 --- a/src/pages/[platform]/build-a-backend/troubleshooting/cannot-find-module-amplify-env/index.mdx +++ b/src/pages/[platform]/build-a-backend/troubleshooting/cannot-find-module-amplify-env/index.mdx @@ -25,6 +25,8 @@ export function getStaticProps(context) { }; } + + When deploying a Amplify Gen 2 app, you may encounter the error message `Cannot find module $amplify/env/` in your frontend build on Amplify Console. This error occurs when your framework `tsconfig.json` configuration picks up the `amplify` directory and tries to resolve it as a module. This module is a placeholder for environment variables that are injected at build time by Amplify. To resolve this error, you need to exclude the `amplify` directory. To exclude the `amplify` directory in your `tsconfig.json`, add the following lines to the `exclude` section: @@ -49,3 +51,23 @@ Alternatively, if you work within a monorepo you can move your backend to its ow } } ``` + + + + + + +When deploying a Amplify Gen 2 app, you may encounter the error message `Cannot find module $amplify/env/` in your frontend build on Amplify Console. This error occurs when your framework `tsconfig.json` configuration picks up the `amplify` directory and tries to resolve it as a module. This module is a placeholder for environment variables that are injected at build time by Amplify. To resolve this error, you will need to include the `resource.ts` files in your `tsconfig.app.json` file. + +For example, if you have a `function` resource dependent on the `data` resource, you will need to include both the `resource.ts` files in your `tsconfig.app.json` file. + +```ts title='tsconfig.app.json' +{ + "include": [ + "amplify/data/resource.ts", + "amplify/function/api-function/resource.ts", + ] +} +``` + + From 56b6bc077908fba8ed34d3273012ff45e811e813 Mon Sep 17 00:00:00 2001 From: Dane Pilcher Date: Thu, 12 Dec 2024 13:29:55 -0700 Subject: [PATCH 20/25] add guide to extend api key expiration and rotate api key (#8158) * add guide to extend api key expiration and rotate key * add some addtional details --- .../public-data-access/index.mdx | 50 ++++++++++++++++++- 1 file changed, 48 insertions(+), 2 deletions(-) diff --git a/src/pages/[platform]/build-a-backend/data/customize-authz/public-data-access/index.mdx b/src/pages/[platform]/build-a-backend/data/customize-authz/public-data-access/index.mdx index df2beb9f9f6..35cdcc15d1c 100644 --- a/src/pages/[platform]/build-a-backend/data/customize-authz/public-data-access/index.mdx +++ b/src/pages/[platform]/build-a-backend/data/customize-authz/public-data-access/index.mdx @@ -79,7 +79,7 @@ In your application, you can perform CRUD operations against the model by specif try { final todo = Todo(content: 'My new todo'); final request = ModelMutations.create( - todo, + todo, authorizationMode: APIAuthorizationType.apiKey, ); final createdTodo = await Amplify.API.mutations(request: request).response; @@ -112,6 +112,52 @@ do {
+### Extend API Key Expiration + +If the API key has not expired, you can extend the expiration date by deploying your app again. The API key expiration date will be set to `expiresInDays` days from the date when the app is deployed. In the example below, the API key will expire 7 days from the latest deployment. + +```ts title="amplify/data/resource.ts" +export const data = defineData({ + schema, + authorizationModes: { + defaultAuthorizationMode: 'apiKey', + apiKeyAuthorizationMode: { + expiresInDays: 7, + }, + }, +}); +``` + +### Rotate an API Key + +You can rotate an API key if it was expired, compromised, or deleted. To rotate an API key, you can override the logical ID of the API key resource in the `amplify/backend.ts` file. This will create a new API key with a new logical ID. + +```ts title="amplify/backend.ts" +const backend = defineBackend({ + auth, + data, +}); + +backend.data.resources.cfnResources.cfnApiKey?.overrideLogicalId( + `recoverApiKey${new Date().getTime()}` +); +``` + +Deploy your app. After the deploy has finished, remove the override to the logical ID and deploy your app again to use the default logical ID. + +```ts title="amplify/backend.ts" +const backend = defineBackend({ + auth, + data, +}); + +// backend.data.resources.cfnResources.cfnApiKey?.overrideLogicalId( +// `recoverApiKey${new Date().getTime()}` +// ); +``` + +A new API key will be created for your app. + ## Add public authorization rule using Amazon Cognito identity pool's unauthenticated role You can also override the authorization provider. In the example below, `identityPool` is specified as the provider which allows you to use an "Unauthenticated Role" from the Cognito identity pool for public access instead of an API key. Your Auth resources defined in `amplify/auth/resource.ts` generates scoped down IAM policies for the "Unauthenticated role" in the Cognito identity pool automatically. @@ -182,7 +228,7 @@ In your application, you can perform CRUD operations against the model with the try { final todo = Todo(content: 'My new todo'); final request = ModelMutations.create( - todo, + todo, authorizationMode: APIAuthorizationType.iam, ); final createdTodo = await Amplify.API.mutations(request: request).response; From c8ee4aa7217fc0ddffdb4111aec85c2e19b80f83 Mon Sep 17 00:00:00 2001 From: bobbyu99 Date: Thu, 12 Dec 2024 13:31:52 -0800 Subject: [PATCH 21/25] chore: update one of the numbered list (#8134) --- src/pages/[platform]/build-ui/formbuilder/index.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/[platform]/build-ui/formbuilder/index.mdx b/src/pages/[platform]/build-ui/formbuilder/index.mdx index 290345995f3..c453d9ce31f 100644 --- a/src/pages/[platform]/build-ui/formbuilder/index.mdx +++ b/src/pages/[platform]/build-ui/formbuilder/index.mdx @@ -91,7 +91,7 @@ Amplify.configure(outputs); import { TodoCreateForm } from './ui-components'; ``` -3. Place your form in code. For a form named `ProductCreateForm` in a React project, you could use the following App code: +4. Place your form in code. For a form named `ProductCreateForm` in a React project, you could use the following App code: ```jsx function App() { From 3a0073ff7c9bf946ad602f779ee3b8400909c78b Mon Sep 17 00:00:00 2001 From: AllanZhengYP Date: Thu, 12 Dec 2024 14:35:45 -0800 Subject: [PATCH 22/25] docs(js): support uploadData checksumAlgorithm option (#8162) --- .../build-a-backend/storage/upload-files/index.mdx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/pages/[platform]/build-a-backend/storage/upload-files/index.mdx b/src/pages/[platform]/build-a-backend/storage/upload-files/index.mdx index a4f264a9491..e3ce5cbf30d 100644 --- a/src/pages/[platform]/build-a-backend/storage/upload-files/index.mdx +++ b/src/pages/[platform]/build-a-backend/storage/upload-files/index.mdx @@ -1568,9 +1568,11 @@ const result = await uploadData({ // whether to use accelerate endpoint useAccelerateEndpoint: true, // the account ID that owns requested bucket - expectedBucketOwner: '123456789012', + expectedBucketOwner: "123456789012", // whether to check if an object with the same key already exists before completing the upload preventOverwrite: true, + // whether to compute the checksum for the data to be uploaded, so the S3 can verify the data integrity + checksumAlgorithm: "crc-32", // only 'crc-32' is supported currently }, }); ``` @@ -1584,6 +1586,7 @@ Option | Type | Default | Description | | useAccelerateEndpoint | boolean | false | Whether to use accelerate endpoint.

Read more at [Transfer Acceleration](/[platform]/build-a-backend/storage/upload-files/#transfer-acceleration) | | expectedBucketOwner | string | - | The account ID that owns requested bucket. | | preventOverwrite | boolean | false | Whether to check if an object with the same key already exists before completing the upload. If exists, a `Precondition Failed` error will be thrown | +| checksumAlgorithm | "crc-32" | - | Whether to compute the checksum for the data to be uploaded, so the S3 can verify the data integrity. Only 'crc-32' is supported currently | From 874906a2d4c9c1f4abc85ce363f71560c1759e47 Mon Sep 17 00:00:00 2001 From: Ian Saultz <52051793+atierian@users.noreply.github.com> Date: Fri, 13 Dec 2024 16:26:53 -0500 Subject: [PATCH 23/25] add region callout for gen1 predictions directive (#8111) --- .../connect-machine-learning-services/index.mdx | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/pages/gen1/[platform]/build-a-backend/graphqlapi/connect-machine-learning-services/index.mdx b/src/pages/gen1/[platform]/build-a-backend/graphqlapi/connect-machine-learning-services/index.mdx index bb29be49e06..21d8dde7324 100644 --- a/src/pages/gen1/[platform]/build-a-backend/graphqlapi/connect-machine-learning-services/index.mdx +++ b/src/pages/gen1/[platform]/build-a-backend/graphqlapi/connect-machine-learning-services/index.mdx @@ -253,6 +253,17 @@ enum PredictionsActions { } ``` + + +The `@predictions` directive relies on the following AWS services, which must support the region in which the AppSync API is deployed: + +- `identifyText` uses [Amazon Rekognition](https://docs.aws.amazon.com/general/latest/gr/rekognition.html) +- `identifyLabels` uses [Amazon Rekognition](https://docs.aws.amazon.com/general/latest/gr/rekognition.html) +- `convertTextToSpeech` uses [Amazon Polly](https://docs.aws.amazon.com/general/latest/gr/pol.html) +- `translateText` uses [Amazon Translate](https://docs.aws.amazon.com/general/latest/gr/translate-service.html) + + + `@predictions` creates resources to communicate with Amazon Rekognition, Translate, and Polly. For each action the following is created: - IAM Policy for each service (e.g. Amazon Rekognition `detectText` Policy) From a0012914f648486c5e89621e6519925aead1164a Mon Sep 17 00:00:00 2001 From: James Jarvis Date: Tue, 17 Dec 2024 13:20:48 -0800 Subject: [PATCH 24/25] adding autosignin callout (#8168) --- .../auth/connect-your-frontend/sign-in/index.mdx | 5 ++++- 1 file changed, 4 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 59c955c5fee..5f23407352f 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 @@ -830,7 +830,7 @@ signInWithRedirect({ provider: { }}) ``` -### Auto sign-in +## Auto sign-in The `autoSignIn` API will automatically sign-in a user when it was previously enabled by the `signUp` API and after any of the following cases has completed: @@ -842,6 +842,9 @@ import { autoSignIn } from 'aws-amplify/auth'; await autoSignIn(); ``` + +**Note**: When MFA is enabled, your users may be presented with multiple consecutive steps that require them to enter an OTP to proceed with the sign up and subsequent sign in flow. This requirement is not present when using the `USER_AUTH` flow. + Date: Mon, 23 Dec 2024 11:31:56 -0500 Subject: [PATCH 25/25] Access groups example (#8174) * remove reference of renameModelFields * add example for accessing user groups from session * simplify example and add some spacing * revert change * add back ticks --- .../user-group-based-data-access/index.mdx | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/pages/[platform]/build-a-backend/data/customize-authz/user-group-based-data-access/index.mdx b/src/pages/[platform]/build-a-backend/data/customize-authz/user-group-based-data-access/index.mdx index 934b3421718..3c32f50abbc 100644 --- a/src/pages/[platform]/build-a-backend/data/customize-authz/user-group-based-data-access/index.mdx +++ b/src/pages/[platform]/build-a-backend/data/customize-authz/user-group-based-data-access/index.mdx @@ -168,3 +168,19 @@ By default, `group` authorization leverages Amazon Cognito user pool groups but - subscriptions are only supported if the user is part of 20 or fewer groups - you can only authorize 20 or fewer user groups per record + +## Access user groups from the session + + + +You can access a user's groups from their session using the Auth category: + +```ts +import { fetchAuthSession } from 'aws-amplify/auth'; + +const session = await fetchAuthSession(); +const groups = session.tokens.accessToken.payload['cognito:groups'] || []; + +console.log('User groups:', groups); +``` +