diff --git a/src/directory/directory.mjs b/src/directory/directory.mjs index d8bcb643a59..a5be302bf9e 100644 --- a/src/directory/directory.mjs +++ b/src/directory/directory.mjs @@ -417,6 +417,9 @@ export const directory = { }, { path: 'src/pages/[platform]/build-a-backend/functions/examples/s3-upload-confirmation/index.mdx' + }, + { + path: 'src/pages/[platform]/build-a-backend/functions/examples/custom-auth-flows/index.mdx' } ] }, 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 e18fd879ad7..dbddf87f4bb 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 @@ -160,7 +160,11 @@ in addition to a password in order to verify the identity of users. These challe or dynamic challenge questions. The `CUSTOM_WITH_SRP` flow requires a password when calling `signIn`. Both of these flows map to the `CUSTOM_AUTH` flow in Cognito. -To define your challenges for custom authentication flow, you need to implement three Lambda triggers for Amazon Cognito. + + +To define your challenges for custom authentication flow, you need to implement three Lambda triggers for Amazon Cognito. Please visit [AWS Amplify Custom Auth Challenge example](/[platform]/build-a-backend/functions/examples/custom-auth-flows/) for set up instructions. + + 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 new file mode 100644 index 00000000000..7b62b66f3d8 --- /dev/null +++ b/src/pages/[platform]/build-a-backend/functions/examples/custom-auth-flows/index.mdx @@ -0,0 +1,229 @@ +import { getCustomStaticPath } from '@/utils/getCustomStaticPath'; + +export const meta = { + title: 'Custom Auth Challenge', + description: + 'Leverage Custom Auth with and without SRP, allowing for a series of challenge and response cycles that can be customized to meet different requirements during sign in.', + platforms: [ + 'android', + 'angular', + 'flutter', + 'javascript', + 'nextjs', + 'react', + 'react-native', + 'swift', + 'vue' + ] +}; + +export function getStaticPaths() { + return getCustomStaticPath(meta.platforms); +} + +export function getStaticProps() { + return { + props: { + meta + } + }; +} + +Secure Remote Password (SRP) is a cryptographic protocol enabling password-based authentication without transmitting the password over the network. In Amazon Cognito custom authentication flows, CUSTOM_WITH_SRP incorporates SRP steps for enhanced security, while CUSTOM_WITHOUT_SRP bypasses these for a simpler process. The choice between them depends on your application's security needs and performance requirements. +This guide demonstrates how to implement both types of custom authentication flows using AWS Amplify with Lambda triggers. + +You can use `defineAuth` and `defineFunction` to create an auth experience that uses `CUSTOM_WITH_SRP` and `CUSTOM_WITHOUT_SRP`. This can be accomplished by leveraging [Amazon Cognito's feature to define a custom auth challenge](https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-authentication-flow.html#Custom-authentication-flow-and-challenges) and 3 triggers: + +1. [Create auth challenge](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-create-auth-challenge.html) +2. [Define auth challenge](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-define-auth-challenge.html) +3. [Verify auth challenge response](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-verify-auth-challenge-response.html) + +To get started, install the `aws-lambda` package, which is used to define the handler type. + +```bash title="Terminal" showLineNumbers={false} +npm add --save-dev @types/aws-lambda +``` + +## Create auth challenge trigger + +To get started, create the first of the three triggers, `create-auth-challenge`. This is the trigger responsible for creating the reCAPTCHA challenge after a password is verified. + +```ts title="amplify/auth/create-auth-challenge/resource.ts" +import { defineFunction } from "@aws-amplify/backend" + +export const createAuthChallenge = defineFunction({ + name: "create-auth-challenge", +}) +``` + +After creating the resource file, create the handler with the following contents: + +```ts title="amplify/auth/create-auth-challenge/handler.ts" +import type { CreateAuthChallengeTriggerHandler } from "aws-lambda"; + +export const handler: CreateAuthChallengeTriggerHandler = async (event) => { + if (event.request.challengeName === "CUSTOM_CHALLENGE") { + // Generate a random code for the custom challenge + const challengeCode = "123456"; + + event.response.challengeMetadata = "TOKEN_CHECK"; + + event.response.publicChallengeParameters = { + trigger: "true", + code: challengeCode, + }; + + event.response.privateChallengeParameters = { trigger: "true" }; + event.response.privateChallengeParameters.answer = challengeCode; + } + return event; +}; +``` + +## Define auth challenge trigger + +Next, you will want to create the trigger responsible for _defining_ the auth challenge flow, `define-auth-challenge`. + +```ts title="amplify/auth/define-auth-challenge/resource.ts" +import { defineFunction } from "@aws-amplify/backend" + +export const defineAuthChallenge = defineFunction({ + name: "define-auth-challenge", +}) +``` + +After creating the resource file, create the handler with the following contents if you are using `CUSTOM_WITHOUT_SRP`: + +```ts title="amplify/auth/define-auth-challenge/handler.ts" +import type { DefineAuthChallengeTriggerHandler } from "aws-lambda" + +export const handler: DefineAuthChallengeTriggerHandler = async (event) => { + // Check if this is the first authentication attempt + if (event.request.session.length === 0) { + // For the first attempt, we start with the custom challenge + event.response.issueTokens = false; + event.response.failAuthentication = false; + event.response.challengeName = "CUSTOM_CHALLENGE"; + } else if ( + event.request.session.length === 1 && + event.request.session[0].challengeName === "CUSTOM_CHALLENGE" && + event.request.session[0].challengeResult === true + ) { + // If this is the second attempt (session length 1), + // it was a CUSTOM_CHALLENGE, and the result was successful + event.response.issueTokens = true; + event.response.failAuthentication = false; + } else { + // If we reach here, it means either: + // 1. The custom challenge failed + // 2. We've gone through more attempts than expected + // In either case, we fail the authentication + event.response.issueTokens = false; + event.response.failAuthentication = true; + } + + return event; +}; +``` + +Or if you are using `CUSTOM_WITH_SRP`: + +```ts title="amplify/auth/define-auth-challenge/handler.ts" +import type { DefineAuthChallengeTriggerHandler } from "aws-lambda" + +export const handler: DefineAuthChallengeTriggerHandler = async (event) => { + // First attempt: Start with SRP_A (Secure Remote Password protocol, step A) + if (event.request.session.length === 0) { + event.response.issueTokens = false; + event.response.failAuthentication = false; + event.response.challengeName = "SRP_A"; + } else if ( + event.request.session.length === 1 && + event.request.session[0].challengeName === "SRP_A" && + event.request.session[0].challengeResult === true + ) { + // Second attempt: SRP_A was successful, move to PASSWORD_VERIFIER + 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 + ) { + // Third attempt: PASSWORD_VERIFIER was successful, move to CUSTOM_CHALLENGE + 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 + ) { + // Fourth attempt: CUSTOM_CHALLENGE was successful, authentication complete + event.response.issueTokens = true; + event.response.failAuthentication = false; + } else { + // If we reach here, it means one of the challenges failed or + // we've gone through more attempts than expected + event.response.issueTokens = false; + event.response.failAuthentication = true; + } + + return event; +}; +``` + +## Verify auth challenge response trigger + +Lastly, create the trigger responsible for _verifying_ the challenge response. For the purpose of this example, the verification check will always return true. + +```ts title="amplify/auth/verify-auth-challenge-response/resource.ts" +import { defineFunction, secret } from "@aws-amplify/backend" + +export const verifyAuthChallengeResponse = defineFunction({ + name: "verify-auth-challenge-response", +}) +``` + +After creating the resource file, create the handler with the following contents: + +```ts title="amplify/auth/verify-auth-challenge-response/handler.ts" +import type { VerifyAuthChallengeResponseTriggerHandler } from "aws-lambda" + +export const handler: VerifyAuthChallengeResponseTriggerHandler = async ( + event +) => { + event.response.answerCorrect = true; + return event; +}; + +``` + +## Configure auth resource + +Finally, import and set the three triggers on your auth resource: + +```ts title="amplify/auth/resource.ts" +import { defineAuth } from "@aws-amplify/backend" +import { createAuthChallenge } from "./create-auth-challenge/resource" +import { defineAuthChallenge } from "./define-auth-challenge/resource" +import { verifyAuthChallengeResponse } from "./verify-auth-challenge-response/resource" + +/** + * Define and configure your auth resource + * @see https://docs.amplify.aws/gen2/build-a-backend/auth + */ +export const auth = defineAuth({ + loginWith: { + email: true, + }, + triggers: { + createAuthChallenge, + defineAuthChallenge, + verifyAuthChallengeResponse, + }, +}) +``` + +After deploying the changes, whenever a user attempts to sign in with `CUSTOM_WITH_SRP` or `CUSTOM_WITHOUT_SRP`, the Lambda challenges will be triggered.