Amplify has re-imagined the way frontend developers build fullstack applications. Develop and deploy without the hassle.

Page updated May 1, 2024

Google reCAPTCHA challenge

You can use defineAuth and defineFunction to create an auth experience that requires a reCAPTCHA v3 token. This can be accomplished by leveraging Amazon Cognito's feature to define a custom auth challenge and 3 triggers:

  1. Create auth challenge
  2. Define auth challenge
  3. Verify auth challenge response

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.

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:

amplify/auth/create-auth-challenge/handler.ts
import type { CreateAuthChallengeTriggerHandler } from "aws-lambda"
export const handler: CreateAuthChallengeTriggerHandler = async (event) => {
const { request, response } = event
if (
// session will contain 3 "steps": SRP, password verification, custom challenge
request.session.length === 2 &&
request.challengeName === "CUSTOM_CHALLENGE"
) {
response.publicChallengeParameters = { trigger: "true" }
response.privateChallengeParameters = { answer: "" }
// optionally set challenge metadata
response.challengeMetadata = "CAPTCHA_CHALLENGE"
}
return event
}

Define auth challenge trigger

Next, you will want to create the trigger responsible for defining the auth challenge flow, define-auth-challenge.

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:

amplify/auth/define-auth-challenge/handler.ts
import type { DefineAuthChallengeTriggerHandler } from "aws-lambda"
export const handler: DefineAuthChallengeTriggerHandler = async (event) => {
const { response } = event
const [srp, password, captcha] = event.request.session
// deny by default
response.issueTokens = false
response.failAuthentication = true
if (srp?.challengeName === "SRP_A") {
response.failAuthentication = false
response.challengeName = "PASSWORD_VERIFIER"
}
if (
password?.challengeName === "PASSWORD_VERIFIER" &&
password.challengeResult === true
) {
response.failAuthentication = false
response.challengeName = "CUSTOM_CHALLENGE"
}
if (
captcha?.challengeName === "CUSTOM_CHALLENGE" &&
// check for the challenge metadata set in "create-auth-challenge"
captcha?.challengeMetadata === "CAPTCHA_CHALLENGE" &&
captcha.challengeResult === true
) {
response.issueTokens = true
response.failAuthentication = false
}
return event
}

Verify auth challenge response trigger

Lastly, create the trigger responsible for verifying the challenge response, which in this case is the reCAPTCHA token verification.

If you have not done so already, you will need to register your application and retrieve a reCAPTCHA secret key. This can then be configured for use with your cloud sandbox using:

Terminal
npx ampx sandbox secret set GOOGLE_RECAPTCHA_SECRET_KEY
amplify/auth/verify-auth-challenge-response/resource.ts
import { defineFunction, secret } from "@aws-amplify/backend"
export const verifyAuthChallengeResponse = defineFunction({
name: "verify-auth-challenge-response",
environment: {
GOOGLE_RECAPTCHA_SECRET_KEY: secret("GOOGLE_RECAPTCHA_SECRET_KEY"),
},
})

After creating the resource file, create the handler with the following contents:

amplify/auth/verify-auth-challenge-response/handler.ts
import type { VerifyAuthChallengeResponseTriggerHandler } from "aws-lambda"
import { env } from "$amplify/env/verify-auth-challenge-response"
/**
* Google ReCAPTCHA verification response
* @see https://developers.google.com/recaptcha/docs/v3#site_verify_response
*/
type GoogleRecaptchaVerifyResponse = {
// whether this request was a valid reCAPTCHA token for your site
success: boolean
// the score for this request (0.0 - 1.0)
score: number
// the action name for this request (important to verify)
action: string
// timestamp of the challenge load (ISO format yyyy-MM-dd'T'HH:mm:ssZZ)
challenge_ts: string
// the hostname of the site where the reCAPTCHA was solved
hostname: string
// optional error codes
"error-codes"?: unknown[]
}
export const handler: VerifyAuthChallengeResponseTriggerHandler = async (
event
) => {
if (!event.request.challengeAnswer) {
throw new Error("Missing challenge answer")
}
// https://developers.google.com/recaptcha/docs/verify#api_request
const url = new URL("https://www.google.com/recaptcha/api/siteverify")
const params = new URLSearchParams({
secret: env.GOOGLE_RECAPTCHA_SECRET_KEY,
response: event.request.challengeAnswer,
})
url.search = params.toString()
const request = new Request(url, {
method: "POST",
})
const response = await fetch(request)
const result = (await response.json()) as GoogleRecaptchaVerifyResponse
if (!result.success) {
throw new Error("Verification failed", { cause: result["error-codes"] })
}
// indicate whether the answer is correct
event.response.answerCorrect = result.success
return event
}

Configure auth resource

Finally, import and set the three triggers on your auth resource:

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,
},
})