Skip to Content

reCAPTCHA

Introduction

Google reCAPTCHA v3 is used in our frontend to help protect sensitive flows from bots and abuse. It runs in the background (no checkbox), produces a score-based token.

Verification happens on the backend. The frontend does not verify the token. Our apps only obtain the token from Google and send it to the API; the backend is responsible for validating it with Google’s reCAPTCHA API.

Current scope: reCAPTCHA is implemented in two projects onlylogin and account.


Where reCAPTCHA is used

ProjectFlow / screenUsage
AccountChange password (request)Token sent with changePasswordRequest (request code).
AccountChange password (form)Token can be sent with changePassword (submit new password).
AccountChange email (request)Token sent when requesting the code.
AccountChange email (form/confirm)Token sent on submit and on confirm.
AccountVerify emailToken sent with verifyEmail (POST account/confirm/email).
AccountSetup 2FAToken sent for generate secret and for verify.
AccountReset passwordProvider + widget and useRecaptcha are present; sending recaptcha_token in the API call is currently commented out in src/components/ResetPassword/index.tsx. Error handling still checks for “recaptcha” in the message.
LoginLogin processreCAPTCHA is used in the login flow as well.
Login2FA processreCAPTCHA is used during the 2FA process.
LoginReset passwordreCAPTCHA is used in the reset password flow.

How it works

  1. Frontend: The app loads the reCAPTCHA script (via a provider), and an invisible widget gets a token from Google when needed or when we ask for a refresh.
  2. Frontend: The token is stored (in a hook) and sent to our API as recaptcha_token in the request body.
  3. Backend: The API receives the token and validates it with Google’s reCAPTCHA API. The frontend never performs this verification.

Configuration is done via the site key only, using the env variable NEXT_PUBLIC_RECAPTCHA_SITE_KEY in environment files (e.g. environments/.env.dev, .env.staging, .env.prod). The secret key used for server-side verification stays on the backend.


Tech stack


Structure

1. The useRecaptcha hook (@/hooks/useRecaptcha)

This hook is the central place for the reCAPTCHA token and refresh behaviour:

  • captchaToken — The current token string. It is set when the reCAPTCHA widget runs and calls onVerify.
  • setCaptchaToken — Updates the token when a new one is issued; also used to clear the token after a successful submit so the next action gets a fresh one.
  • refreshRecaptcha — A boolean that tells the widget to request a new token. When it is set to true, the library runs again and calls onVerify with a new token.
  • setRefreshRecaptcha — Set to true when you want to force a token refresh (e.g. after submit, on error, or when the user navigates).

When the token is refreshed:

  • On route change: When the pathname changes, refreshRecaptcha is set to true so each page gets a new token.
  • On a timer: When a token already exists, an interval runs every 90 seconds and sets refreshRecaptcha to true. Tokens last about 2 minutes; refreshing at 90 seconds keeps them valid.

2. UI: Provider and widget

Any flow that needs reCAPTCHA wraps its content in:

  1. GoogleReCaptchaProvider — Loads the reCAPTCHA script. It must receive the site key, e.g. reCaptchaKey={process.env.NEXT_PUBLIC_RECAPTCHA_SITE_KEY || ""}.
  2. GoogleReCaptcha — The invisible widget that:
    • Runs on mount and whenever refreshReCaptcha becomes true,
    • Calls onVerify(token) with the new token; containers pass setCaptchaToken so the hook keeps the latest token,
    • Does not use a custom action in this codebase (same pattern as phpreaction-frontend-login-react).

Containers use GoogleReCaptcha directly from react-google-recaptcha-v3. A thin wrapper RecaptchaToken exists in @/components/recaptcha (same props: onVerify, refreshReCaptcha) for reuse but is not used by the current containers; they use the library components inline.

3. Sending the token to the API

The token is sent as recaptcha_token in the request body. API helpers in @/utils/apiUser.ts accept an optional recaptcha_token in their body types, for example:

  • changePasswordRequest(tenant, { csrf, recaptcha_token })
  • changePassword(body) where body can include recaptcha_token
  • Change-email and 2FA flows also send recaptcha_token in the body.
Last updated on