---
title: "Custom auth flows"
section: "build-a-backend/auth/customize-auth-lifecycle"
platforms: ["android", "flutter", "swift"]
gen: 2
last-updated: "2026-03-25T17:40:00.000Z"
url: "https://docs.amplify.aws/react/build-a-backend/auth/customize-auth-lifecycle/custom-auth-flows/"
---

<!-- Platform: swift -->
The Auth category can be configured to perform a [custom authentication flow](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-challenge.html) defined by you. The following guide shows how to setup a simple passwordless authentication flow.

## Prerequisites
An application with Amplify libraries integrated and a minimum target of any of the following:
- **iOS 13.0**, using **Xcode 14.1** or later.
- **macOS 10.15**, using **Xcode 14.1** or later.
- **tvOS 13.0**, using **Xcode 14.3** or later.
- **watchOS 9.0**, using **Xcode 14.3** or later.
- **visionOS 1.0**, using **Xcode 15** or later. (Preview support - see below for more details.)

For a full example, please follow the [project setup walkthrough](/[platform]/start/quickstart/).

<Callout>

visionOS support is currently in **preview** and can be used by using the latest [Amplify Release](https://github.com/aws-amplify/amplify-swift/releases). 
As new Xcode and visionOS versions are released, the support will be updated with any necessary fixes on a best effort basis.

</Callout>

<Callout>

To use Auth in a macOS project, you'll need to enable the Keychain Sharing capability. In Xcode, navigate to **your application target** > **Signing & Capabilities** > **+ Capability**, then select **Keychain Sharing.**

This capability is required because Auth uses the Data Protection Keychain on macOS as a platform best practice. See [TN3137: macOS keychain APIs and implementations](https://developer.apple.com/documentation/technotes/tn3137-on-mac-keychains) for more information on how Keychain works on macOS and the Keychain Sharing entitlement.

For more information on adding capabilities to your application, see [Xcode Capabilities](https://developer.apple.com/documentation/xcode/capabilities).

</Callout>

## Configure Auth

The custom auth flow can be [configured manually](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-challenge.html).

## Sign in a user

Implement a UI to get the username from the user. After the user enters the username you can start the sign in flow by calling the following method:

#### [Async/Await]

```swift
func signIn(username: String) async {
    do {
        let options = AWSAuthSignInOptions(authFlowType: .customWithoutSRP)
        let signInResult = try await Amplify.Auth.signIn(username: username,
                                                        options: .init(pluginOptions: options))
        if case .confirmSignInWithCustomChallenge(_) = signInResult.nextStep {
            // Ask the user to enter the custom challenge.
        } else {
            print("Sign in succeeded")
        }
    } catch let error as AuthError {
        print("Sign in failed \(error)")
    } catch {
        print("Unexpected error: \(error)")
    }
}
```

#### [Combine]

```swift
func signIn(username: String) -> AnyCancellable {
    Amplify.Publisher.create {
        let options = AWSAuthSignInOptions(authFlowType: .customWithoutSRP)
        try await Amplify.Auth.signIn(username: username,
                                    options: .init(pluginOptions: options))
    }.sink {
        if case let .failure(authError) = $0 {
            print("Sign in failed \(authError)")
        }
    }
    receiveValue: { result in
        if case .confirmSignInWithCustomChallenge(_) = result.nextStep {
            // Ask the user to enter the custom challenge.
        } else {
            print("Sign in succeeded")
        }
    }
}
```

Since this is a custom authentication flow with a challenge, the result of the signin process has a next step `.confirmSignInWithCustomChallenge`. Implement a UI to allow the user to enter the custom challenge.

## Confirm sign in with custom challenge

To get a custom challenge from the user, create an appropriate UI for the user to submit the required value, and pass that value into the `confirmSignin()` API.

#### [Async/Await]

```swift
func customChallenge(response: String) async {
    do {
      _ = try await Amplify.Auth.confirmSignIn(challengeResponse: response)
      print("Confirm sign in succeeded") 
    } catch let error as AuthError {
      print("Confirm sign in failed \(error)")
    } catch {
      print("Unexpected error: \(error)")
    }
}
```

#### [Combine]

```swift
func customChallenge(response: String) -> AnyCancellable {
    Amplify.Publisher.create {
        try await Amplify.Auth.confirmSignIn(challengeResponse: response)
        }.sink {
            if case let .failure(authError) = $0 {
                print("Confirm sign in failed \(authError)")
            }
        }
        receiveValue: { _ in
            print("Confirm sign in succeeded")
        }
}
```

You will know the sign in flow is complete if you see the following in your console window:

```console
Confirm sign in succeeded
```

### Lambda Trigger Setup

AWS Amplify now supports creating functions as part of its new backend experience. For more information on the Functions and how to start with them check out [Functions documentation](/[platform]/build-a-backend/functions/). In addition, more information on available triggers can be found in the [Cognito documentation](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-identity-pools-working-with-aws-lambda-triggers.html).

### Custom Auth Flow with Secure Remote Password (SRP)

Cognito User Pool allows to start the custom authentication flow with SRP as the first step. If you would like to use this flow, setup Define Auth Lambda trigger to handle SRP_A as the first challenge as shown below:

```javascript
exports.handler = (event, context) => {
  if (event.request.session.length == 1 && 
      event.request.session[0].challengeName == 'SRP_A') {
        event.response.issueTokens = false;
        event.response.failAuthentication = false;
        event.response.challengeName = 'PASSWORD_VERIFIER';
  } else if (event.request.session.length == 2 && 
      event.request.session[1].challengeName == 'PASSWORD_VERIFIER' && 
      event.request.session[1].challengeResult == true) {
        event.response.issueTokens = false;
        event.response.failAuthentication = false;
        event.response.challengeName = 'CUSTOM_CHALLENGE';
  } else if (event.request.session.length == 3 && 
      event.request.session[2].challengeName == 'CUSTOM_CHALLENGE' && 
      event.request.session[2].challengeResult == true) {
        event.response.issueTokens = true;
        event.response.failAuthentication = false;
  } else {
      event.response.issueTokens = false;
      event.response.failAuthentication = true;
  }
  context.done(null, event);
};
```

If your lambda is setup to start with `SRP` as the first step, make sure to initiate the signIn process with `customWithSRP` as the authentication flow:

```swift
let options = AWSAuthSignInOptions(
    authFlowType: .customWithSRP)
let signInResult = try await Amplify.Auth.signIn(
    username: username,
    password: password,
    options: .init(pluginOptions: options))
```
<!-- /Platform -->
<!-- Platform: android -->
The Auth category can be configured to perform a [custom authentication flow](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-challenge.html) defined by you. The following guide shows how to setup a simple passwordless authentication flow.

## Prerequisites

* An Android application targeting at least Android SDK API level 24 with Amplify libraries integrated
    * For a full example of creating Android project, please follow the [project setup walkthrough](/[platform]/start/quickstart/)

## Configure Auth

The custom auth flow can be [configured manually](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-challenge.html).

## Register a user

The flow as mentioned above requires a username and a valid email id as parameters to register a user. Invoke the following api to initiate a sign up flow.

#### [Java]

```java
AuthSignUpOptions options = AuthSignUpOptions.builder()
    .userAttribute(AuthUserAttributeKey.email(), "my@email.com")
    .build();
Amplify.Auth.signUp("username", "Password123", options,
    result -> Log.i("AuthQuickStart", "Result: " + result.toString()),
    error -> Log.e("AuthQuickStart", "Sign up failed", error)
);
```

#### [Kotlin - Callbacks]

```kotlin
val options = AuthSignUpOptions.builder()
    .userAttribute(AuthUserAttributeKey.email(), "my@email.com")
    .build()
Amplify.Auth.signUp("username", "Password123", options,
    { Log.i("AuthQuickStart", "Sign up succeeded: $it") },
    { Log.e ("AuthQuickStart", "Sign up failed", it) }
)
```

#### [Kotlin - Coroutines]

```kotlin
val options = AuthSignUpOptions.builder()
    .userAttribute(AuthUserAttributeKey.email(), "my@email.com")
    .build()
try {
    val result = Amplify.Auth.signUp("username", "Password123", options)
    Log.i("AuthQuickStart", "Result: $result") 
} catch (error: AuthException) {
    Log.e("AuthQuickStart", "Sign up failed", error)
}
```

#### [RxJava]

 ```java
RxAmplify.Auth.signUp(
    "username",
    "Password123",
    AuthSignUpOptions.builder().userAttribute(AuthUserAttributeKey.email(), "my@email.com").build())
    .subscribe(
        result -> Log.i("AuthQuickStart", "Result: " + result.toString()),
        error -> Log.e("AuthQuickStart", "Sign up failed", error)
    );
```

The next step in the sign up flow is to confirm the user. A confirmation code will be sent to the email id provided during sign up. Enter the confirmation code received via email in the `confirmSignUp` call.

#### [Java]

```java
Amplify.Auth.confirmSignUp(
    "username",
    "the code you received via email",
    result -> Log.i("AuthQuickstart", result.isSignUpComplete() ? "Confirm signUp succeeded" : "Confirm sign up not complete"),
    error -> Log.e("AuthQuickstart", error.toString())
);
```

#### [Kotlin - Callbacks]

```kotlin
Amplify.Auth.confirmSignUp(
    "username", "the code you received via email",
    { result ->
        if (result.isSignUpComplete) {
            Log.i("AuthQuickstart", "Confirm signUp succeeded")
        } else {
            Log.i("AuthQuickstart","Confirm sign up not complete")
        }
    },
    { Log.e("AuthQuickstart", "Failed to confirm sign up", it) }
)
```

#### [Kotlin - Coroutines]

```kotlin
try {
    val code = "code you received via email"
    val result = Amplify.Auth.confirmSignUp("username", code)
    if (result.isSignUpComplete) {
        Log.i("AuthQuickstart", "Signup confirmed")
    } else {
        Log.i("AuthQuickstart", "Signup confirmation not yet complete")
    }
} catch (error: AuthException) {
    Log.e("AuthQuickstart", "Failed to confirm signup", error)
}
```

#### [RxJava]

```java
RxAmplify.Auth.confirmSignUp("username", "the code you received via email")
    .subscribe(
        result -> Log.i("AuthQuickstart", result.isSignUpComplete() ? "Confirm signUp succeeded" : "Confirm sign up not complete"),
        error -> Log.e("AuthQuickstart", error.toString())
    );
```

You will know the sign up flow is complete if you see the following in your console window:

```console
Confirm signUp succeeded
```

## Sign in a user

Implement a UI to get the username from the user. After the user enters the username you can start the sign in flow by calling the following method:

#### [Java]

```java
AWSCognitoAuthSignInOptions options = AWSCognitoAuthSignInOptions.builder()
    .authFlowType(AuthFlowType.CUSTOM_AUTH_WITHOUT_SRP)
    .build();
Amplify.Auth.signIn(
    "username",
    "password",
    options,
    result -> Log.i("AuthQuickstart", result.isSignedIn() ? "Sign in succeeded" : "Sign in not complete"),
    error -> Log.e("AuthQuickstart", error.toString())
);
```

#### [Kotlin - Callbacks]

```kotlin
val options = AWSCognitoAuthSignInOptions.builder()
    .authFlowType(AuthFlowType.CUSTOM_AUTH_WITHOUT_SRP)
    .build()
Amplify.Auth.signIn(
    "username", 
    "password", 
    options,
    { result ->
        if (result.isSignedIn) {
            Log.i("AuthQuickstart", "Sign in succeeded")
        } else {
            Log.i("AuthQuickstart", "Sign in not complete")
        }
    },
    { Log.e("AuthQuickstart", "Failed to sign in", it) }
)
```

#### [Kotlin - Coroutines]

```kotlin
val options = AWSCognitoAuthSignInOptions.builder()
    .authFlowType(AuthFlowType.CUSTOM_AUTH_WITHOUT_SRP)
    .build()
try {
    val result = Amplify.Auth.signIn("username", "password", options)
    if (result.isSignedIn) {
        Log.i("AuthQuickstart", "Sign in succeeded")
    } else {
        Log.e("AuthQuickstart", "Sign in not complete")
    }
} catch (error: AuthException) {
    Log.e("AuthQuickstart", "Sign in failed", error)
}
```

#### [RxJava]

```java
AWSCognitoAuthSignInOptions options = AWSCognitoAuthSignInOptions.builder()
    .authFlowType(AuthFlowType.CUSTOM_AUTH_WITHOUT_SRP)
    .build();
RxAmplify.Auth.signIn("username", "password", options)
    .subscribe(
        result -> Log.i("AuthQuickstart", result.isSignedIn() ? "Sign in succeeded" : "Sign in not complete"),
        error -> Log.e("AuthQuickstart", error.toString())
    );
```

Since this is a custom authentication flow with a challenge, the result of the signin process has a next step `.confirmSignInWithCustomChallenge`. Implement a UI to allow the user to enter the custom challenge.

## Confirm sign in with custom challenge

Get the custom challenge (`1234` in this case) from the user and pass it to the `confirmSignin()` api.

#### [Java]

```java
Amplify.Auth.confirmSignIn(
    "confirmation",
    result -> Log.i("AuthQuickstart", "Confirm sign in succeeded: " + result.toString()),
    error -> Log.e("AuthQuickstart", "Failed to confirm sign in", error)
);
```

#### [Kotlin - Callbacks]

```kotlin
Amplify.Auth.confirmSignIn("confirmation",
    { Log.i("AuthQuickstart", "Confirm sign in succeeded: $it") },
    { Log.e("AuthQuickstart", "Failed to confirm sign in", it) }
)
```

#### [Kotlin - Coroutines]

```kotlin
try {
    val result = Amplify.Auth.confirmSignIn("confirmation")
    Log.i("AuthQuickstart", "Confirm sign in succeeded: $result") 
} catch (error: AuthException) {
    Log.e("AuthQuickstart", "Failed to confirm signin", error)
}
```

#### [RxJava]

```java
RxAmplify.Auth.confirmSignIn("confirmation")
    .subscribe(
        result -> Log.i("AuthQuickstart", result.toString()),
        error -> Log.e("AuthQuickstart", error.toString())
    );
```

You will know the sign in flow is complete if you see the following in your console window:

```console
Confirm sign in succeeded
```

### Lambda Trigger Setup

AWS Amplify now supports creating functions as part of the AWS Amplify. For more information on the Functions and how to start with them check out [Functions documentation](/[platform]/build-a-backend/functions/). In addition, more information on available triggers can be found in the [Cognito documentation](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-identity-pools-working-with-aws-lambda-triggers.html).

### Custom Auth Flow with Secure Remote Password (SRP)

Cognito User Pool allows to start the custom authentication flow with SRP as the first step. If you would like to use this flow, setup Define Auth Lambda trigger to handle SRP_A as the first challenge as shown below:

```javascript
exports.handler = (event, context) => {
  if (event.request.session.length == 1 &&
      event.request.session[0].challengeName == 'SRP_A') {
        event.response.issueTokens = false;
        event.response.failAuthentication = false;
        event.response.challengeName = 'PASSWORD_VERIFIER';
  } else if (event.request.session.length == 2 &&
      event.request.session[1].challengeName == 'PASSWORD_VERIFIER' &&
      event.request.session[1].challengeResult == true) {
        event.response.issueTokens = false;
        event.response.failAuthentication = false;
        event.response.challengeName = 'CUSTOM_CHALLENGE';
  } else if (event.request.session.length == 3 &&
      event.request.session[2].challengeName == 'CUSTOM_CHALLENGE' &&
      event.request.session[2].challengeResult == true) {
        event.response.issueTokens = true;
        event.response.failAuthentication = false;
  } else {
      event.response.issueTokens = false;
      event.response.failAuthentication = true;
  }
  context.done(null, event);
};
```

If your lambda is setup to start with `SRP` as the first step, make sure to initiate the signIn process with `customWithSRP` as the authentication flow:

#### [Java]

```java
AWSCognitoAuthSignInOptions options = AWSCognitoAuthSignInOptions.builder()
    .authFlowType(AuthFlowType.CUSTOM_AUTH_WITH_SRP)
    .build();
Amplify.Auth.signIn(
    "username",
    "password",
    options,
    result -> Log.i("AuthQuickstart", result.isSignedIn() ? "Sign in succeeded" : "Sign in not complete"),
    error -> Log.e("AuthQuickstart", error.toString())
);
```

#### [Kotlin - Callbacks]

```kotlin
val options = AWSCognitoAuthSignInOptions.builder()
    .authFlowType(AuthFlowType.CUSTOM_AUTH_WITH_SRP)
    .build()
Amplify.Auth.signIn(
    "username", 
    "password", 
    options,
    { result ->
        if (result.isSignedIn) {
            Log.i("AuthQuickstart", "Sign in succeeded")
        } else {
            Log.i("AuthQuickstart", "Sign in not complete")
        }
    },
    { Log.e("AuthQuickstart", "Failed to sign in", it) }
)
```

#### [Kotlin - Coroutines]

```kotlin
val options = AWSCognitoAuthSignInOptions.builder()
    .authFlowType(AuthFlowType.CUSTOM_AUTH_WITH_SRP)
    .build()
try {
    val result = Amplify.Auth.signIn("username", "password", options)
    if (result.isSignedIn) {
        Log.i("AuthQuickstart", "Sign in succeeded")
    } else {
        Log.e("AuthQuickstart", "Sign in not complete")
    }
} catch (error: AuthException) {
    Log.e("AuthQuickstart", "Sign in failed", error)
}
```

#### [RxJava]

```java
AWSCognitoAuthSignInOptions options = AWSCognitoAuthSignInOptions.builder()
    .authFlowType(AuthFlowType.CUSTOM_AUTH_WITH_SRP)
    .build();
RxAmplify.Auth.signIn("username", "password", options)
    .subscribe(
        result -> Log.i("AuthQuickstart", result.isSignedIn() ? "Sign in succeeded" : "Sign in not complete"),
        error -> Log.e("AuthQuickstart", error.toString())
    );
```

<!-- /Platform -->

<!-- Platform: flutter -->
The Auth category can be configured to perform a [custom authentication flow](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-challenge.html) defined by you. The following guide shows how to setup a simple passwordless authentication flow.

## Prerequisites

Amplify requires a minimum target platform for iOS (13.0), Android (API level 24), and macOS (10.15). Refer to [Flutter's supported deployment platforms](https://docs.flutter.dev/reference/supported-platforms) when targeting Web, Windows, or Linux. Additional setup is required for some target platforms. Please see the [platform setup](/[platform]/frontend/auth/sign-in/#platform-setup) for more details on platform specific setup.

## Configure Auth

The custom auth flow can be [configured manually](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-challenge.html).

## Register a user

The flow as mentioned above requires a username and a valid email id as parameters to register a user. Invoke the following api to initiate a sign up flow.

Because authentication flows in Cognito can be switched via your configuration, it is still required that users register with a password.

```dart
Future<void> signUpCustomFlow() async {
  try {
    final userAttributes = <AuthUserAttributeKey, String>{
      AuthUserAttributeKey.email: 'email@domain.com',
      AuthUserAttributeKey.phoneNumber: '+15559101234',
      // additional attributes as needed
    };
    final result = await Amplify.Auth.signUp(
      username: 'myusername',
      password: 'mysupersecurepassword',
      options: SignUpOptions(userAttributes: userAttributes),
    );
    safePrint('Sign up result: $result');
  } on AuthException catch (e) {
    safePrint('Error signing up: ${e.message}');
  }
}
```

The next step in the sign up flow is to confirm the user. A confirmation code will be sent to the email id provided during sign up. Enter the confirmation code received via email in the `confirmSignUp` call.

```dart
Future<void> confirmUser({
  required String username,
  required String confirmationCode,
}) async {
  try {
    final result = await Amplify.Auth.confirmSignUp(
      username: username,
      confirmationCode: confirmationCode,
    );
    // Check if further confirmations are needed or if
    // the sign up is complete.
    await _handleSignUpResult(result);
  } on AuthException catch (e) {
    safePrint('Error confirming user: ${e.message}');
  }
}
```

## Sign in a user

Implement a UI to get the username from the user. After the user enters the username you can start the sign in flow by calling the following method:

```dart
// Create state variables for the sign in status
var isSignedIn = false;
String? challengeHint;

Future<void> signInCustomFlow(String username) async {
  try {
    final result = await Amplify.Auth.signIn(username: username);
    setState(() {
      isSignedIn = result.isSignedIn;
      // Get the publicChallengeParameters from your Create Auth Challenge Lambda
      challengeHint = result.nextStep.additionalInfo['hint'];
    });
  } on AuthException catch (e) {
    safePrint('Error signing in: ${e.message}');
  }
}
```

<Callout>

Please note that you will be prevented from successfully calling `signIn` if a
user has already signed in and a valid session is active. You must first call
`signOut` to remove the original session.

</Callout>
## Confirm sign in with custom challenge

To get a custom challenge from the user, create an appropriate UI for the user to submit the required value, and pass that value into the `confirmSignin()` API.

```dart
Future<void> confirmSignIn(String generatedNumber) async {
  try {
    final result = await Amplify.Auth.confirmSignIn(
      /// Enter the random number generated by your Create Auth Challenge trigger
      confirmationValue: generatedNumber,
    );
    safePrint('Sign in result: $result');
  } on AuthException catch (e) {
    safePrint('Error signing in: ${e.message}');
  }
}
```

Once the user provides the correct response, they should be authenticated in your application.

> **Warning:** <b>Special Handling on ConfirmSignIn</b>
> 
> During a `confirmSignIn` call, if `failAuthentication: true` is returned by the Lambda, the session of the request gets invalidated by Cognito, and a `NotAuthorizedException` is thrown. To recover, the user must initiate a new sign in by calling `Amplify.Auth.signIn`.
> 
> Exception: `NotAuthorizedException` with a message `Invalid session for the user.`

## Custom authentication flow with password verification

The example in this documentation demonstrates the passwordless custom authentication flow. However, it is also possible to require that users supply a valid password as part of the custom authentication flow.

To require a valid password, you can alter the [DefineAuthChallenge](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-define-auth-challenge.html) code to handle a `PASSWORD_VERIFIER` step:

```js
exports.handler = async (event) => {
  if (
    event.request.session.length === 1 &&
    event.request.session[0].challengeName === 'SRP_A'
  ) {
    event.response.issueTokens = false;
    event.response.failAuthentication = false;
    event.response.challengeName = 'PASSWORD_VERIFIER';
  } else if (
    event.request.session.length === 2 &&
    event.request.session[1].challengeName === 'PASSWORD_VERIFIER' &&
    event.request.session[1].challengeResult === true
  ) {
    event.response.issueTokens = false;
    event.response.failAuthentication = false;
    event.response.challengeName = 'CUSTOM_CHALLENGE';
  } else if (
    event.request.session.length === 3 &&
    event.request.session[2].challengeName === 'CUSTOM_CHALLENGE' &&
    event.request.session[2].challengeResult === true
  ) {
    event.response.issueTokens = true;
    event.response.failAuthentication = false;
  } else {
    event.response.issueTokens = false;
    event.response.failAuthentication = true;
  }

  return event;
};
```
<!-- /Platform -->
