Captcha (Bot Protection)
This feature is in alpha. APIs and behavior may change without notice. Not recommended for production use.
EdgeBase uses Cloudflare Turnstile to automatically protect authentication endpoints from bots. With zero configuration required for most setups.
Quick Start
Add one line to your config:
// edgebase.config.ts
export default defineConfig({
captcha: true,
});
That's it. When you run npx edgebase deploy, Turnstile keys are automatically provisioned, stored, and distributed to your SDKs.
How It Works
Client SDK Server Cloudflare
│ │ │
├─ GET /api/config ────────►│ │
│◄─── { captcha: { siteKey } } │
│ │ │
├─ Turnstile widget ───────────────────────────────────►│
│◄── token (invisible, ~200ms) ◄────────────────────────│
│ │ │
├─ POST /auth/signup ──────►│ │
│ { captchaToken: "..." } ├─ siteverify(token) ──────►│
│ │◄── { success: true } ◄────│
│◄── 201 Created ◄─────────│ │
Phase 1: Invisible (99% of users)
Turnstile runs invisibly in the background. Users see nothing — the token is acquired automatically in ~200ms.
Phase 2: Interactive Challenge (1% of users)
If Cloudflare detects suspicious behavior, an interactive challenge (checkbox or puzzle) is shown as an overlay. The SDK handles this automatically.
Configuration
Zero-Config (Cloudflare Deploy)
captcha: true
npx edgebase deployauto-provisions Turnstile widget via Cloudflare Management APIsiteKeyis exposed to clients viaCAPTCHA_SITE_KEYandGET /api/configsecretKeyis stored as Workers SecretTURNSTILE_SECRET
Manual Keys (Self-Hosting / Docker)
captcha: {
siteKey: '0x4AAAAAAA...', // From Turnstile dashboard
secretKey: '0x4AAAAAAA...',
failMode: 'open', // 'open' (default) | 'closed'
siteverifyTimeout: 3000, // ms (default: 3000)
}
Disable Captcha
captcha: false // or omit entirely
SDK Usage
Zero-Config (Recommended)
Client SDKs handle captcha automatically once the runtime host is in place. No per-request code changes are needed:
- JavaScript
- Dart/Flutter
- Swift
- Kotlin
- Java
- C#
- C++
const client = createClient('https://your-project.edgebase.fun');
await client.auth.signUp({ email: 'user@test.com', password: 'pass123' });
// SDK automatically: fetches siteKey → runs Turnstile → attaches token
final client = ClientEdgeBase('...');
await client.auth.signUp(SignUpOptions(email: 'user@test.com', password: 'pass123'));
let client = EdgeBaseClient("...")
try await client.auth.signUp(email: "user@test.com", password: "pass123")
val client = ClientEdgeBase("...")
client.auth.signUp(email = "user@test.com", password = "pass123")
ClientEdgeBase client = EdgeBase.client("...");
client.auth().signUp("user@test.com", "pass123");
var client = new EdgeBase("...");
await client.Auth.SignUpAsync("user@test.com", "pass123");
eb::EdgeBase client("...");
client.auth().signUp("user@test.com", "pass123");
Client Runtime Support Matrix
If you are comparing package/module layout instead of runtime support, see SDK Layer Matrix.
Legend:
✅: supported and validated in a current example/runtime flow◐: supported by the SDK or target host, but still depends on a specific host/plugin or has not been fully re-validated in every runtime—: no client-side captcha path
| SDK | Android | iOS | macOS | Windows | Linux | Web |
|---|---|---|---|---|---|---|
@edgebase/web | — | — | ✅ | ◐ | ◐ | ✅ |
@edgebase/react-native | ✅ | ✅ | — | — | — | ✅ |
edgebase_flutter | ✅ | ✅ | ✅ | ◐ | ◐ | ✅ |
EdgeBase Swift | — | ✅ | ✅ | — | — | — |
Kotlin KMP client | ✅ | ✅ | ✅ | — | — | ✅ |
Android Java | ✅ | — | — | — | — | — |
Unity C# | ✅ | ✅ | ✅ | ◐ | ◐ | ✅ |
Unreal / C++ | ✅ | ✅ | ✅ | ◐ | ◐ | — |
Notes:
@edgebase/webdesktop columns mean browser-hosted runtimes such as Electron renderer processes, not a native desktop-only SDK.Kotlin KMP clientuses a no-op JVM captcha provider, so Windows/Linux desktop JVM targets are intentionally not marked supported here.Unity C#desktop support depends on a supported WebView host. The current macOS path is validated through an embeddedgree/unity-webviewwindow. Other desktop targets still require a supported host integration or a customTurnstileProvider.SetWebViewFactory(...).Unreal / C++uses the built-in browser runtime on supported targets; macOS, Android, and iOS are validated in the current example app flow.
Manual Token Override
If you need custom captcha UI or use a different captcha provider:
await client.auth.signUp({
email: 'user@test.com',
password: 'pass123',
captchaToken: myCustomToken, // Skips built-in Turnstile
});
Protected Endpoints
| Endpoint | Action | Captcha |
|---|---|---|
POST /auth/signup | signup | ✅ |
POST /auth/signin | signin | ✅ |
POST /auth/signin/anonymous | anonymous | ✅ |
POST /auth/request-password-reset | password-reset | ✅ |
POST /auth/signin/magic-link | magic-link | ✅ |
POST /auth/signin/phone | phone | ✅ |
GET /auth/oauth/:provider | oauth | ✅ |
POST /auth/refresh | — | ❌ (session renewal) |
POST /auth/signout | — | ❌ (logout) |
POST /auth/change-password | — | ❌ (authenticated) |
Functions with Captcha
You can enable captcha on individual HTTP functions:
// functions/submit-form.ts
export default defineFunction({
trigger: { type: 'http' },
captcha: true, // Requires captcha token
handler: async (context) => { ... },
});
Platform-Specific Details
Web (JS/TS, Flutter Web, Kotlin JS)
Turnstile JS SDK is loaded directly into the browser DOM. No WebView needed.
Android (Kotlin, Java)
- Uses
android.webkit.WebView(system built-in, zero dependencies) - Zero-config: Automatically detects
Applicationcontext viaActivityThread.currentApplication()reflection - Automatically tracks current foreground
ActivityviaActivityLifecycleCallbacks - Interactive challenges shown as dimmed overlay on current Activity
iOS/macOS (Swift, Kotlin Apple)
- Uses
WKWebView(system built-in, zero dependencies) - Interactive challenges shown as overlay on key window
- iOS:
UIWindowScene→keyWindowoverlay - macOS:
NSApplication.keyWindowoverlay
Unity (C#)
Built-in adapters for popular WebView plugins (auto-detected at startup):
| Plugin | Define Symbol | License |
|---|---|---|
| UniWebView | UNIWEBVIEW | Paid |
| Vuplex 3D WebView | VUPLEX_WEBVIEW | Paid |
| gree/unity-webview | UNITY_WEBVIEW_GREE | Free (MIT) |
Add the define symbol in Player Settings → Scripting Define Symbols. The adapter auto-registers at startup.
If no supported plugin is detected, a warning is logged. You can provide a custom factory:
TurnstileProvider.SetWebViewFactory(async (siteKey, action) => {
var html = TurnstileProvider.GetTurnstileHtml(siteKey, action);
// Load html in your WebView, capture token from JS bridge
return token;
});
Unreal Engine (C++)
Uses the built-in SWebBrowser widget (CEF-based). Zero third-party dependencies.
Setup: Add to your .Build.cs:
PublicDependencyModuleNames.AddRange(new string[] {
"WebBrowserWidget", "Slate", "SlateCore"
});
Then include the header in any .cpp file:
#include "edgebase/turnstile_adapter_ue.h"
The adapter auto-registers via FCoreDelegates::OnPostEngineInit. No manual calls needed.
Flutter (Dart)
Uses flutter_inappwebview for all native platforms (Android, iOS, macOS, Windows, Linux). Already included as a dependency in the SDK.
# Already in edgebase SDK's pubspec.yaml
dependencies:
flutter_inappwebview: ^6.0.0
No additional setup needed. Platform detection is automatic via conditional imports.
Security
| Aspect | Detail |
|---|---|
| siteKey | Public — safely distributed via GET /api/config |
| secretKey | Private — stored as Workers Secret TURNSTILE_SECRET, never exposed |
| Token one-use | Cloudflare auto-invalidates tokens after siteverify |
| Action verification | Prevents token reuse across endpoints (signup token can't be used for signin) |
| Service Key bypass | Server SDKs (Go, PHP, Rust, Python) bypass captcha when using Service Keys |
| CDN caching | /api/config cached for 60s at edge (reduces Worker invocations) |
Fail Modes
| Mode | Behavior on Turnstile API Failure | Use Case |
|---|---|---|
open (default) | Allow request through, log warning | General apps (availability first) |
closed | Reject with 503 | Finance, healthcare (security first) |
Rate Limiting + Captcha
Captcha and rate limiting work together as complementary layers:
Request → CORS → Rate Limit → Captcha → Auth Handler
- Rate limiting runs first — blocks brute-force regardless of captcha status
- Captcha runs second — verifies human origin for requests that pass rate limit
- Both layers are independent — disabling one doesn't affect the other
- Service Keys bypass captcha and EdgeBase app-level rate limits
Testing
Disable in Tests (Recommended)
# wrangler.test.toml — omit captcha config / CAPTCHA_SITE_KEY
Cloudflare Test Keys
| Purpose | siteKey | secretKey |
|---|---|---|
| Always passes | 1x00000000000000000000AA | 1x0000000000000000000000000000000AA |
| Always fails | 2x00000000000000000000AB | 2x0000000000000000000000000000000AB |
| Forces interactive | 3x00000000000000000000FF | 3x0000000000000000000000000000000FF |
// Use in test config for E2E captcha testing
captcha: {
siteKey: '1x00000000000000000000AA',
secretKey: '1x0000000000000000000000000000000AA',
}
Troubleshooting
Captcha not working in development
captcha: true requires Cloudflare deploy for auto-provisioning. For local development, either:
- Use manual keys:
captcha: { siteKey: '...', secretKey: '...' } - Disable captcha:
captcha: false
Interactive challenge keeps appearing
This usually means Cloudflare's bot detection is triggered. Common causes:
- Running from a datacenter IP
- Automated testing without test keys
- VPN or proxy usage
SDK not automatically acquiring tokens
Verify GET /api/config returns { captcha: { siteKey: "..." } }. If captcha is null, captcha is not configured on the server.