diff --git a/docs/src/pages/[platform]/connected-components/authenticator/configuration/index.page.mdx b/docs/src/pages/[platform]/connected-components/authenticator/configuration/index.page.mdx index 58dc372255..0ec7ee2f71 100644 --- a/docs/src/pages/[platform]/connected-components/authenticator/configuration/index.page.mdx +++ b/docs/src/pages/[platform]/connected-components/authenticator/configuration/index.page.mdx @@ -44,6 +44,8 @@ import HideSignupSwift from './hidesignup.swift.mdx'; import HideSignupWeb from './hidesignup.web.mdx'; import TotpSwift from './totpIssuer.swift.mdx'; import TotpAndroid from './totpIssuer.android.mdx'; +import PasswordlessSwift from './passwordless.swift.mdx'; +import PasswordlessAndroid from './passwordless.android.mdx'; export async function getStaticPaths() { return getCustomStaticPath(frontmatter.supportedFrameworks); @@ -72,6 +74,8 @@ By default, unauthenticated users are redirected to the Sign In flow. You can ex + + @@ -81,6 +85,8 @@ By default, unauthenticated users are redirected to the Sign In flow. You can ex + + ## Sign Up Attributes diff --git a/docs/src/pages/[platform]/connected-components/authenticator/configuration/passwordless.android.mdx b/docs/src/pages/[platform]/connected-components/authenticator/configuration/passwordless.android.mdx new file mode 100644 index 0000000000..530acb6d2c --- /dev/null +++ b/docs/src/pages/[platform]/connected-components/authenticator/configuration/passwordless.android.mdx @@ -0,0 +1,113 @@ +import { Alert } from '@aws-amplify/ui-react'; + +### Passwordless Authentication + + + Passkey support requires Android API level 28+ with Google Play Services. + + +#### Backend Configuration + +For backend setup including Email OTP, SMS OTP, and WebAuthn (Passkey) configuration, see the [Passwordless documentation](https://docs.amplify.aws/android/build-a-backend/auth/concepts/passwordless/). + +#### Authentication Flows + +The Authenticator supports two authentication flows: + +**Password Flow (Default)** + +Traditional password-based authentication: + +```kotlin +Authenticator( + authenticationFlow = AuthenticationFlow.Password +) { state -> + Text("Welcome!") +} +``` + +**User Choice Flow** + +Allows users to choose from available authentication methods: + +```kotlin +Authenticator( + authenticationFlow = AuthenticationFlow.UserChoice() +) { state -> + Text("Welcome!") +} +``` + +#### Available Authentication Factors + +| Factor | Description | Code | +|--------|-------------|------| +| Password | Traditional password with SRP | `AuthFactor.Password(srp = true)` | +| Password (no SRP) | Password without SRP | `AuthFactor.Password(srp = false)` | +| Email OTP | One-time code via email | `AuthFactor.EmailOtp` | +| SMS OTP | One-time code via SMS | `AuthFactor.SmsOtp` | +| WebAuthn (Passkey) | Biometric/security key authentication | `AuthFactor.WebAuthn` | + +#### Preferred Auth Factor + +Skip the factor selection step by specifying a preferred factor: + +```kotlin +Authenticator( + authenticationFlow = AuthenticationFlow.UserChoice( + preferredAuthFactor = AuthFactor.WebAuthn + ) +) { state -> + Text("Welcome!") +} +``` + +When a preferred factor is set: +- If the user has that factor available, they'll use it directly +- If not available, they'll see the factor selection screen + +#### Passkey Prompts + +Control when users are prompted to create a passkey: + +```kotlin +Authenticator( + authenticationFlow = AuthenticationFlow.UserChoice( + passkeyPrompts = PasskeyPrompts( + afterSignUp = PasskeyPrompt.Always, // Prompt after sign-up + afterSignIn = PasskeyPrompt.Never // Don't prompt after sign-in + ) + ) +) { state -> + Text("Welcome!") +} +``` + +**PasskeyPrompt Options:** +- `PasskeyPrompt.Always` - Always prompt to create a passkey (if user doesn't have one) +- `PasskeyPrompt.Never` - Never prompt to create a passkey + +#### Authentication Flow Steps + +When using `UserChoice` flow, users may encounter these additional steps: + +1. **SignInSelectAuthFactor** - User selects their preferred authentication method +2. **SignInConfirmPassword** - User enters password (if password factor selected) +3. **PromptToCreatePasskey** - User is prompted to create a passkey +4. **PasskeyCreated** - Confirmation after successful passkey creation + +#### Complete Example + +```kotlin +Authenticator( + authenticationFlow = AuthenticationFlow.UserChoice( + preferredAuthFactor = AuthFactor.WebAuthn, + passkeyPrompts = PasskeyPrompts( + afterSignUp = PasskeyPrompt.Always, + afterSignIn = PasskeyPrompt.Always + ) + ) +) { state -> + Text("Welcome ${state.user.username}!") +} +``` diff --git a/docs/src/pages/[platform]/connected-components/authenticator/configuration/passwordless.swift.mdx b/docs/src/pages/[platform]/connected-components/authenticator/configuration/passwordless.swift.mdx new file mode 100644 index 0000000000..a93ed95393 --- /dev/null +++ b/docs/src/pages/[platform]/connected-components/authenticator/configuration/passwordless.swift.mdx @@ -0,0 +1,109 @@ +import { Alert } from '@aws-amplify/ui-react'; + +### Passwordless Authentication + + + Passkey support requires iOS 17.4+, macOS 13.5+, or visionOS 1.0+. + + +#### Backend Configuration + +For backend setup including Email OTP, SMS OTP, and WebAuthn (Passkey) configuration, see the [Passwordless documentation](https://docs.amplify.aws/swift/build-a-backend/auth/concepts/passwordless/). + +#### Authentication Flows + +The Authenticator supports two authentication flows: + +**Password Flow (Default)** + +Traditional password-based authentication: + +```swift +Authenticator(authenticationFlow: .password) { signedInState in + Text("Welcome!") +} +``` + +**User Choice Flow** + +Allows users to choose from available authentication methods: + +```swift +Authenticator(authenticationFlow: .userChoice()) { signedInState in + Text("Welcome!") +} +``` + +#### Available Authentication Factors + +| Factor | Description | Code | +|--------|-------------|------| +| Password | Traditional password with SRP | `.password(srp: true)` | +| Password (no SRP) | Password without SRP | `.password(srp: false)` | +| Email OTP | One-time code via email | `.emailOtp` | +| SMS OTP | One-time code via SMS | `.smsOtp` | +| WebAuthn (Passkey) | Biometric/security key authentication | `.webAuthn` | + +#### Preferred Auth Factor + +Skip the factor selection step by specifying a preferred factor: + +```swift +Authenticator( + authenticationFlow: .userChoice( + preferredAuthFactor: .webAuthn + ) +) { signedInState in + Text("Welcome!") +} +``` + +When a preferred factor is set: +- If the user has that factor available, they'll use it directly +- If not available, they'll see the factor selection screen + +#### Passkey Prompts + +Control when users are prompted to create a passkey: + +```swift +Authenticator( + authenticationFlow: .userChoice( + passkeyPrompts: PasskeyPrompts( + afterSignUp: .always, // Prompt after sign-up + afterSignIn: .never // Don't prompt after sign-in + ) + ) +) { signedInState in + Text("Welcome!") +} +``` + +**PasskeyPrompt Options:** +- `.always` - Always prompt to create a passkey (if user doesn't have one) +- `.never` - Never prompt to create a passkey + +#### Authentication Flow Steps + +When using `.userChoice` flow, users may encounter these additional steps: + +1. **signInSelectAuthFactor** - User selects their preferred authentication method +2. **signInConfirmPassword** - User enters password (if password factor selected) +3. **promptToCreatePasskey** - User is prompted to create a passkey +4. **passkeyCreated** - Confirmation after successful passkey creation + +#### Complete Example + +```swift +Authenticator( + authenticationFlow: .userChoice( + preferredAuthFactor: .webAuthn, + passkeyPrompts: PasskeyPrompts( + afterSignUp: .always, + afterSignIn: .always + ) + ) +) { signedInState in + Text("Welcome \(signedInState.user.username)!") +} +``` diff --git a/docs/src/pages/[platform]/connected-components/authenticator/customization/customization.passwordless-views.android.mdx b/docs/src/pages/[platform]/connected-components/authenticator/customization/customization.passwordless-views.android.mdx new file mode 100644 index 0000000000..d49868977e --- /dev/null +++ b/docs/src/pages/[platform]/connected-components/authenticator/customization/customization.passwordless-views.android.mdx @@ -0,0 +1,75 @@ +import { Alert } from '@aws-amplify/ui-react'; + +### Customizing Passwordless Views + +When using `AuthenticationFlow.UserChoice`, you can customize the additional views that appear during the authentication flow. Below is an example of customizing the Passkey views. + +#### Prompt To Create Passkey View + +Customize the passkey creation prompt that appears after sign-in or sign-up: + +```kotlin +Authenticator( + authenticationFlow = AuthenticationFlow.UserChoice(), + promptToCreatePasskeyContent = { state -> + Column { + Text("Create a Passkey") + Text("Sign in faster with biometrics") + + Button(onClick = { + scope.launch { state.createPasskey() } + }) { + Text("Create Passkey") + } + + Button(onClick = { + scope.launch { state.skip() } + }) { + Text("Skip for now") + } + } + } +) { state -> + Text("Welcome!") +} +``` + +**PromptToCreatePasskeyState Actions:** +- `createPasskey()` - Initiates passkey creation using the device's biometric/security key authentication +- `skip()` - Skips passkey creation and continues to the signed-in state + +#### Passkey Created View + +Customize the confirmation screen shown after successful passkey creation: + +```kotlin +Authenticator( + authenticationFlow = AuthenticationFlow.UserChoice(), + passkeyCreatedContent = { state -> + Column { + Text("Passkey Created Successfully!") + Text("You have ${state.passkeys.size} passkey(s)") + + state.passkeys.forEach { passkey -> + Card { + Text(passkey.friendlyName ?: "Unknown Passkey") + } + } + + Button(onClick = { + scope.launch { state.continueSignIn() } + }) { + Text("Continue") + } + } + } +) { state -> + Text("Welcome!") +} +``` + +**PasskeyCreatedState Properties:** +- `passkeys: List` - List of user's passkey credentials + +**PasskeyCreatedState Actions:** +- `continueSignIn()` - Continues to the signed-in state diff --git a/docs/src/pages/[platform]/connected-components/authenticator/customization/customization.passwordless-views.swift.mdx b/docs/src/pages/[platform]/connected-components/authenticator/customization/customization.passwordless-views.swift.mdx new file mode 100644 index 0000000000..c8a19317da --- /dev/null +++ b/docs/src/pages/[platform]/connected-components/authenticator/customization/customization.passwordless-views.swift.mdx @@ -0,0 +1,101 @@ +import { Alert } from '@aws-amplify/ui-react'; + +### Customizing Passwordless Views + +When using `.userChoice` authentication flow, you can customize the additional views that appear during the authentication flow. Below is an example of customizing the Passkey views. + +#### Prompt To Create Passkey View + +Customize the passkey creation prompt that appears after sign-in or sign-up: + +```swift +Authenticator( + authenticationFlow: .userChoice(), + promptToCreatePasskeyContent: { state in + VStack(spacing: 24) { + Image(systemName: "person.badge.key.fill") + .font(.system(size: 60)) + .foregroundColor(.blue) + + Text("Create a Passkey") + .font(.title) + + Text("Sign in faster with Face ID or Touch ID") + .foregroundColor(.secondary) + .multilineTextAlignment(.center) + + Button("Create Passkey") { + Task { try? await state.createPasskey() } + } + .buttonStyle(.borderedProminent) + + Button("Skip for now") { + Task { try? await state.skip() } + } + .buttonStyle(.borderless) + } + .padding() + } +) { signedInState in + Text("Welcome!") +} +``` + +**PromptToCreatePasskeyState Methods:** +- `createPasskey()` - Initiates passkey creation using the device's biometric/security key authentication +- `skip()` - Skips passkey creation and continues to the signed-in state + +#### Passkey Created View + +Customize the confirmation screen shown after successful passkey creation: + +```swift +Authenticator( + authenticationFlow: .userChoice(), + passkeyCreatedContent: { state in + VStack(spacing: 24) { + Image(systemName: "checkmark.circle.fill") + .font(.system(size: 60)) + .foregroundColor(.green) + + Text("Passkey Created!") + .font(.title) + + if !state.passkeyCredentials.isEmpty { + VStack(alignment: .leading, spacing: 8) { + Text("Your Passkeys") + .font(.headline) + + ForEach(state.passkeyCredentials, id: \.credentialId) { credential in + HStack { + Image(systemName: "key.fill") + Text(credential.friendlyName ?? "Unknown Passkey") + } + .padding() + .background(Color.secondary.opacity(0.1)) + .cornerRadius(8) + } + } + } + + Button("Continue") { + Task { try? await state.continue() } + } + .buttonStyle(.borderedProminent) + } + .padding() + .task { + await state.fetchPasskeyCredentials() + } + } +) { signedInState in + Text("Welcome!") +} +``` + +**PasskeyCreatedState Properties:** +- `passkeyCredentials: [AuthWebAuthnCredential]` - List of user's passkey credentials + +**PasskeyCreatedState Methods:** +- `continue()` - Continues to the signed-in state +- `fetchPasskeyCredentials()` - Fetches the list of passkey credentials diff --git a/docs/src/pages/[platform]/connected-components/authenticator/customization/index.page.mdx b/docs/src/pages/[platform]/connected-components/authenticator/customization/index.page.mdx index 731b072c1e..da11d68a1c 100644 --- a/docs/src/pages/[platform]/connected-components/authenticator/customization/index.page.mdx +++ b/docs/src/pages/[platform]/connected-components/authenticator/customization/index.page.mdx @@ -41,6 +41,8 @@ import StylingWeb from './customization.styling.web.mdx'; import FullAndroid from './customization.full-ui-customization.android.mdx'; import FullFlutter from './customization.full-ui-customization.flutter.mdx'; import FullSwift from './customization.full-ui-customization.swift.mdx'; +import PasswordlessViewsAndroid from './customization.passwordless-views.android.mdx'; +import PasswordlessViewsSwift from './customization.passwordless-views.swift.mdx'; export async function getStaticPaths() { return getCustomStaticPath(frontmatter.supportedFrameworks); @@ -100,6 +102,8 @@ export async function getStaticProps() { + + @@ -127,6 +131,8 @@ export async function getStaticProps() { + + diff --git a/docs/src/pages/[platform]/connected-components/authenticator/customization/sign-up-fields.android.mdx b/docs/src/pages/[platform]/connected-components/authenticator/customization/sign-up-fields.android.mdx index 30d8f36b34..38e21e39ba 100644 --- a/docs/src/pages/[platform]/connected-components/authenticator/customization/sign-up-fields.android.mdx +++ b/docs/src/pages/[platform]/connected-components/authenticator/customization/sign-up-fields.android.mdx @@ -1,3 +1,5 @@ +import { Alert } from '@aws-amplify/ui-react'; + ## Sign Up Field Order The Authenticator allows a custom order of sign up fields on the Sign Up page. @@ -93,4 +95,44 @@ val state = rememberAuthenticatorState( } } ) -``` \ No newline at end of file +``` + +## Password Fields in Passwordless Flow + +When using `AuthenticationFlow.UserChoice`, password fields are **not visible by default** on the sign-up form. This allows users to sign up using passwordless methods like Email OTP, SMS OTP, or Passkeys. + +To explicitly show password fields, add them to your `signUpForm` configuration. You can then set `required = true` or `required = false` based on your requirements: + +```kotlin +Authenticator( + authenticationFlow = AuthenticationFlow.UserChoice(), + signUpForm = { + username() + email() + password(required = true) // Show password field, make it required + confirmPassword(required = true) + } +) { state -> + Text("Welcome!") +} +``` + +Or make password optional while still showing the fields: + +```kotlin +Authenticator( + authenticationFlow = AuthenticationFlow.UserChoice(), + signUpForm = { + username() + email() + password(required = false) // Show password field, make it optional + confirmPassword(required = false) + } +) { state -> + Text("Welcome!") +} +``` + + + When using `AuthenticationFlow.Password`, password fields are **always required** regardless of the `required` parameter. This means the `Password` flow will override any `password()` configuration in `signUpForm`, even if `required` is set to `false`. + diff --git a/docs/src/pages/[platform]/connected-components/authenticator/customization/sign-up-fields.swift.mdx b/docs/src/pages/[platform]/connected-components/authenticator/customization/sign-up-fields.swift.mdx index e3852fe563..299b883bea 100644 --- a/docs/src/pages/[platform]/connected-components/authenticator/customization/sign-up-fields.swift.mdx +++ b/docs/src/pages/[platform]/connected-components/authenticator/customization/sign-up-fields.swift.mdx @@ -1,3 +1,5 @@ +import { Alert } from '@aws-amplify/ui-react'; + ## Sign Up Field Order The Authenticator respects the order of the Sign Up fields that are provided in the `signUpFields(_:)` call. @@ -56,3 +58,44 @@ extension Binding where Value == String { } } ``` + + +## Password Fields in Passwordless Flow + +When using `.userChoice` authentication flow, password fields are **not visible by default** on the sign-up form. This allows users to sign up using passwordless methods like Email OTP, SMS OTP, or Passkeys. + +To explicitly show password fields, add them to your `signUpFields` configuration. You can then set `isRequired: true` or `isRequired: false` based on your requirements: + +```swift +Authenticator( + authenticationFlow: .userChoice() +) { signedInState in + Text("Welcome!") +} +.signUpFields([ + .username(), + .email(), + .password(isRequired: true), // Show password field, make it required + .confirmPassword(isRequired: true) +]) +``` + +Or make password optional while still showing the fields: + +```swift +Authenticator( + authenticationFlow: .userChoice() +) { signedInState in + Text("Welcome!") +} +.signUpFields([ + .username(), + .email(), + .password(isRequired: false), // Show password field, make it optional + .confirmPassword(isRequired: false) +]) +``` + + + When using `.password` authentication flow, password fields are **always required** regardless of the `isRequired` parameter. This means the `.password` flow will override any `.signUpFields(.password())` configuration, even if `isRequired` is set to `false`. +