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