-
Notifications
You must be signed in to change notification settings - Fork 15
added docs for RWK + core for captcha protection #589
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
ethankonk
wants to merge
1
commit into
main
Choose a base branch
from
ethan/captcha-protection
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+558
−1
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| --- | ||
| title: "Captcha Protection" | ||
| description: "Overview of captcha support across Turnkey SDKs, including supported versions and setup guides." | ||
| sidebarTitle: "Captcha Protection" | ||
| --- | ||
|
|
||
| ## Overview | ||
|
|
||
| Turnkey supports [Cloudflare Turnstile](https://developers.cloudflare.com/turnstile/) captcha protection to help prevent automated abuse of authentication endpoints. When enabled, captcha tokens are required for Signup and InitOtp requests made through the Auth Proxy. | ||
|
|
||
| Captcha is enabled per-organization in the [Turnkey Dashboard](https://app.turnkey.com) under the **Wallet Kit** settings page. | ||
|
|
||
| <Warning> | ||
| Once captcha is enabled in the dashboard, **all** Signup and InitOtp requests will require valid captcha tokens. Make sure your application is updated to a supported SDK version and has captcha integrated **before** enabling this setting. | ||
| </Warning> | ||
|
|
||
| ## Supported SDKs | ||
|
|
||
| {/* TODO: UPDATE THE VERSION */} | ||
| <CardGroup> | ||
| <Card title="React Wallet Kit" href="/sdks/react/captcha" icon="react" iconType="brands" horizontal> | ||
| `@turnkey/react-wallet-kit` — **v.x.x.x+** | ||
| </Card> | ||
| <Card title="Core SDK" href="/sdks/typescript-frontend/captcha" icon="js" iconType="brands" horizontal> | ||
| `@turnkey/core` — **v.x.x.x+** | ||
| </Card> | ||
| </CardGroup> | ||
|
|
||
| ## How It Works | ||
|
|
||
| 1. **Enable captcha** in the Turnkey Dashboard under Wallet Kit settings. | ||
| 2. The SDK automatically receives a `turnstileSiteKey` from the Auth Proxy via the `getWalletKitClientParams` request. | ||
| 3. Your application renders a Cloudflare Turnstile widget that generates captcha tokens. | ||
| 4. Captcha tokens are attached to Signup and InitOtp requests as an `X-Captcha-Token` header. | ||
|
|
||
| For detailed integration guides, see the SDK-specific pages linked above. | ||
|
|
||
| ## Recommended Rollout | ||
|
|
||
| <Steps> | ||
| <Step title="Update your SDK"> | ||
| Upgrade to a captcha-supported version of the SDK (see table above). | ||
| </Step> | ||
| <Step title="Integrate captcha in your app"> | ||
| Follow the setup guide for your SDK. The captcha widget will idle silently in the background until captcha is enabled — there is no impact to your users. | ||
| </Step> | ||
| <Step title="Enable captcha in the dashboard"> | ||
| Once your app is deployed with captcha support, toggle the **Captcha** option in the Turnkey Dashboard. From this point on, all Signup and InitOtp requests will require valid tokens. | ||
| </Step> | ||
| </Steps> | ||
|
|
||
| <Note> | ||
| You can verify that your app is ready by checking for the presence of `turnstileSiteKey` in the SDK config. If the key is present, captcha is enabled and your app should be providing tokens with each request. | ||
| </Note> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,241 @@ | ||
| --- | ||
| title: "Captcha Protection" | ||
| description: "Learn how to enable Cloudflare Turnstile captcha protection for your Turnkey Embedded Wallet Kit integration." | ||
| sidebarTitle: "Captcha Protection" | ||
| --- | ||
|
|
||
| <Warning> | ||
| {/* TODO: UPDATE THE VERSION */} | ||
| Captcha support requires `@turnkey/react-wallet-kit` version **x.x.x** or later. Make sure to update your package before proceeding. | ||
| </Warning> | ||
|
|
||
| ## Overview | ||
|
|
||
| The Embedded Wallet Kit supports [Cloudflare Turnstile](https://developers.cloudflare.com/turnstile/) captcha protection to help prevent abuse and ensure that authentication requests are performed by real users. When enabled, fresh captcha tokens are required for **every Signup request and every InitOtp request** (including OTP resends). | ||
|
|
||
| There are two ways to use captcha with the Embedded Wallet Kit: | ||
|
|
||
| 1. **Using the built-in Auth component** — captcha is handled automatically, no additional code required. | ||
| 2. **Building a custom auth UI** — you'll need to integrate Turnstile components yourself in the right places. | ||
|
|
||
| ## Enabling Captcha in the Dashboard | ||
|
|
||
| <Warning> | ||
| As soon as captcha is enabled in the dashboard, **all** Signup and InitOtp requests will require captcha tokens. If your frontend is not set up to provide tokens, authentication will fail. It is **strongly recommended** to deploy your app with captcha components integrated first (they will idle silently in the background), and **then** enable the captcha toggle in the dashboard. This ensures a seamless transition with no downtime. | ||
| </Warning> | ||
|
|
||
| <Steps> | ||
| <Step title="Navigate to Wallet Kit settings"> | ||
| Go to the [Turnkey Dashboard](https://app.turnkey.com), navigate to the **Wallet Kit** page. | ||
|
|
||
| {/* <!-- TODO: Screenshot of navigating to the Wallet Kit page --> */} | ||
| </Step> | ||
|
|
||
| <Step title="Enable the Captcha option"> | ||
| Toggle the **Captcha** option to enable it. | ||
|
|
||
| {/* <!-- TODO: Screenshot of the Captcha toggle --> */} | ||
| </Step> | ||
|
|
||
| <Step title="Save your changes"> | ||
| Click **Save**. From this point on, all Signup and InitOtp requests will require valid captcha tokens. | ||
|
|
||
| {/* <!-- TODO: Screenshot of the save confirmation --> */} | ||
| </Step> | ||
| </Steps> | ||
|
|
||
| ### Detecting captcha status on the frontend | ||
|
|
||
| Once captcha is enabled in the dashboard, the `turnstileSiteKey` field will be present in the config returned by the Embedded Wallet Kit (populated from the `getWalletKitClientParams` request). You can use this to determine whether captcha is active: | ||
|
|
||
| ```tsx | ||
| const { config } = useTurnkey(); | ||
|
|
||
| const isCaptchaEnabled = !!config?.turnstileSiteKey; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. rare double negative, is this correct? |
||
| ``` | ||
|
|
||
| ## Option 1: Using the Built-in Auth Component | ||
|
|
||
| If you're using the Embedded Wallet Kit's built-in auth component (via `handleLogin`), captcha is handled entirely for you. The auth component automatically: | ||
|
|
||
| - Pre-warms a captcha token in the background when the user is not authenticated | ||
| - Shows a visible Turnstile challenge only if Cloudflare requires user interaction | ||
| - Attaches captcha tokens to all Signup and InitOtp requests | ||
| - Manages token refresh for the OTP verification step | ||
|
|
||
| No code changes are required — just enable captcha in the dashboard and update to the latest package version. | ||
|
|
||
| ## Option 2: Custom Auth UI | ||
|
|
||
| If you're building your own auth modal or layout, you'll need to integrate Turnstile components in **two places**: | ||
|
|
||
| 1. **The initial auth screen** — where users enter their email/phone or choose a sign-up method | ||
| 2. **The OTP verification screen** — where users enter the OTP code (and can resend) | ||
|
|
||
| This is because a **fresh captcha token is consumed on each request**. A token used for `initOtp` cannot be reused for a subsequent `signup` or another `initOtp` call. | ||
|
|
||
| ### Install the Turnstile library | ||
|
|
||
| We recommend using [`@marsidev/react-turnstile`](https://github.com/marsidev/react-turnstile), a lightweight React wrapper around the Cloudflare Turnstile widget: | ||
|
|
||
| <CodeGroup> | ||
|
|
||
| ```bash npm | ||
| npm install @marsidev/react-turnstile | ||
| ``` | ||
|
|
||
| ```bash pnpm | ||
| pnpm add @marsidev/react-turnstile | ||
| ``` | ||
|
|
||
| ```bash yarn | ||
| yarn add @marsidev/react-turnstile | ||
| ``` | ||
|
|
||
| </CodeGroup> | ||
|
|
||
| ### Setting up the Turnstile component | ||
|
|
||
| Import the component and types: | ||
|
|
||
| ```tsx | ||
| import { Turnstile, type TurnstileInstance } from "@marsidev/react-turnstile"; | ||
| ``` | ||
|
|
||
| #### Appearance modes | ||
|
|
||
| Cloudflare Turnstile supports several appearance modes. For the best user experience, we recommend: | ||
|
|
||
| - **`"interaction-only"`** — The widget is completely hidden unless Cloudflare determines it needs user interaction. This is ideal for most cases as users won't see anything unless a challenge is required. | ||
| - **`"always"`** — The widget is always visible. Use this if you want users to always see the captcha. | ||
|
|
||
| The Turnkey dashboard configures Turnstile in **Managed** mode, meaning Cloudflare decides whether an interactive challenge is needed. Using `appearance: "interaction-only"` pairs well with this — the widget stays invisible for most users, and only appears when Cloudflare needs interaction. | ||
|
|
||
| ### Auth screen integration | ||
|
|
||
| Add a Turnstile component to your auth screen. This component will generate a token that gets consumed when the user initiates a Signup or InitOtp request. | ||
|
|
||
| ```tsx | ||
| import { useRef, useState } from "react"; | ||
| import { Turnstile, type TurnstileInstance } from "@marsidev/react-turnstile"; | ||
| import { useTurnkey } from "@turnkey/react-wallet-kit"; | ||
|
|
||
| function AuthScreen() { | ||
| const { config } = useTurnkey(); | ||
| const turnstileRef = useRef<TurnstileInstance>(null); | ||
| const [captchaToken, setCaptchaToken] = useState<string | null>(null); | ||
| const [showPrompt, setShowPrompt] = useState(false); | ||
|
|
||
| return ( | ||
| <div> | ||
| {/* Your auth form inputs here */} | ||
|
|
||
| {config?.turnstileSiteKey && ( | ||
| <Turnstile | ||
| ref={turnstileRef} | ||
| siteKey={config.turnstileSiteKey} | ||
| onSuccess={(token) => setCaptchaToken(token)} | ||
| onError={() => setCaptchaToken(null)} | ||
| onExpire={() => setCaptchaToken(null)} | ||
| onBeforeInteractive={() => setShowPrompt(true)} | ||
| options={{ | ||
| appearance: "interaction-only", | ||
| size: "flexible", | ||
| }} | ||
| /> | ||
| )} | ||
|
|
||
| {showPrompt && ( | ||
| <p>Please complete the captcha to continue.</p> | ||
| )} | ||
| </div> | ||
| ); | ||
| } | ||
| ``` | ||
|
|
||
| ### OTP screen integration | ||
|
|
||
| A **second** Turnstile component is needed on the OTP verification screen. This generates fresh tokens for completing the OTP or resending it — both of which are captcha-protected requests. | ||
|
|
||
| ```tsx | ||
| import { useRef, useState } from "react"; | ||
| import { Turnstile, type TurnstileInstance } from "@marsidev/react-turnstile"; | ||
| import { useTurnkey } from "@turnkey/react-wallet-kit"; | ||
|
|
||
| function OtpScreen() { | ||
| const { config } = useTurnkey(); | ||
| const turnstileRef = useRef<TurnstileInstance>(null); | ||
| const [captchaToken, setCaptchaToken] = useState<string | null>(null); | ||
| const [showPrompt, setShowPrompt] = useState(false); | ||
|
|
||
| return ( | ||
| <div> | ||
| {/* Your OTP input here */} | ||
|
|
||
| {config?.turnstileSiteKey && ( | ||
| <Turnstile | ||
| ref={turnstileRef} | ||
| siteKey={config.turnstileSiteKey} | ||
| onSuccess={(token) => setCaptchaToken(token)} | ||
| onError={() => setCaptchaToken(null)} | ||
| onExpire={() => setCaptchaToken(null)} | ||
| onBeforeInteractive={() => setShowPrompt(true)} | ||
| options={{ | ||
| appearance: "interaction-only", | ||
| size: "flexible", | ||
| }} | ||
| /> | ||
| )} | ||
|
|
||
| {showPrompt && ( | ||
| <p>Please complete the captcha to continue.</p> | ||
| )} | ||
| </div> | ||
| ); | ||
| } | ||
| ``` | ||
|
|
||
| ### Consuming tokens | ||
|
|
||
| Each captcha token can only be used **once**. After consuming a token (e.g., when calling `initOtp`), you must reset the widget to generate a new one for the next request: | ||
|
|
||
| ```tsx | ||
| // Consume the token and reset the widget for the next request | ||
| const consumeToken = () => { | ||
| const token = captchaToken; | ||
| setCaptchaToken(null); | ||
| turnstileRef.current?.reset(); | ||
| return token; | ||
| }; | ||
|
|
||
| // Example: passing the token to initOtp | ||
| const handleSendOtp = async () => { | ||
| const token = consumeToken(); | ||
|
|
||
| await initOtp({ | ||
| otpType: "email", | ||
| contact: email, | ||
| ...(token ? { captchaToken: token } : {}), | ||
| }); | ||
| }; | ||
|
|
||
| // Example: passing the token to a signup request | ||
| const handleSignup = async () => { | ||
| const token = consumeToken(); | ||
|
|
||
| await signUpWithPasskey({ | ||
| ...(token ? { captchaToken: token } : {}), | ||
| }); | ||
| }; | ||
| ``` | ||
|
|
||
| <Note> | ||
| The `captchaToken` parameter is accepted by `initOtp`, `completeOtp`, `completeOauth`, `loginOrSignupWithWallet`, and OAuth handle methods. When captcha is not enabled, omitting the token has no effect. | ||
| </Note> | ||
|
|
||
| ## Best Practices | ||
|
|
||
| - **Deploy first, enable second**: Integrate captcha components into your app before enabling captcha in the dashboard. The Turnstile widget will silently idle when captcha is not enabled (no `turnstileSiteKey` in config). | ||
| - **Always reset after consuming**: Call `turnstileRef.current?.reset()` after using a token so the widget can generate a fresh one for the next request. | ||
| - **Handle expiration**: Turnstile tokens expire after a short period. Listen to the `onExpire` callback and clear your stored token so that users aren't submitting stale tokens. | ||
| - **Disable submit buttons while waiting**: Consider disabling auth buttons until a valid captcha token is available to prevent failed requests. | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lets be more descriptive