Skip to content

Commit bdd8c0a

Browse files
[Turnstile] Updates (#27237)
* mobile implementation updates * errors * error codes * edits * Apply suggestions from code review Co-authored-by: ranbel <101146722+ranbel@users.noreply.github.com> --------- Co-authored-by: ranbel <101146722+ranbel@users.noreply.github.com>
1 parent 70f4c97 commit bdd8c0a

File tree

3 files changed

+359
-70
lines changed

3 files changed

+359
-70
lines changed

src/content/docs/turnstile/get-started/mobile-implementation.mdx

Lines changed: 162 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,44 +6,186 @@ sidebar:
66

77
---
88

9-
Turnstile is designed to run in a standard browser environment, which includes mobile devices. On native mobile applications, Turnstile can be used with WebViews. This applies to native web applications for iOS and Android. When implementing Turnstile for mobile, ensure you address the common issues below to avoid integration problems.
9+
Turnstile is designed to run in standard browser environments, which includes mobile devices accessing your website through mobile browsers.
1010

11-
Any modifications to the environment, such as the User Agent, [Content Security Policy settings](/turnstile/reference/content-security-policy/), or domain allowlisting, can disrupt the successful completion of Turnstile challenges. To ensure compatibility, it is recommended to start with a default, unmodified environment and gradually introduce changes, validating Turnstile's functionality after each adjustment.
11+
For native mobile applications, Turnstile can be integrated using WebViews that render your web content containing Turnstile widgets.
1212

13+
---
14+
15+
## WebView integration
16+
17+
WebViews are components that allow native mobile applications to display web content. They embed a browser engine within your native application, enabling you to show web pages, forms, and JavaScript-powered content like Turnstile widgets.
18+
19+
### Requirements
20+
21+
For Turnstile to function properly in WebView, the following requirements must be met.
22+
23+
#### JavaScript support
24+
25+
- JavaScript execution must be enabled.
26+
- DOM storage API must be available.
27+
- Standard web APIs must be accessible.
28+
29+
#### Network access
1330

14-
## WebView configurations
31+
- Access to `challenges.cloudflare.com`
32+
- Support for both HTTP and HTTPS connections.
33+
- Allow connections to `about:blank` and `about:srcdoc`
1534

16-
Turnstile requires specific WebView settings to function properly. For Android implementations, refer to [`setJavaScriptEnabled`](https://developer.android.com/reference/android/webkit/WebSettings#setJavaScriptEnabled(boolean)) to tell the WebView to enable JavaScript execution and [`setDomStorageEnabled`](https://developer.android.com/reference/android/webkit/WebSettings#setDomStorageEnabled(boolean)) to enable the DOM storage API.
35+
#### Environment consistency
1736

18-
These settings ensure that the mobile WebView can properly load and execute the Turnstile challenge. If these configurations are missing, Turnstile may malfunction.
37+
- Consistent User Agent throughout the session
38+
- Stable device and browser characteristics
39+
- No modification to core browser behavior
1940

20-
## Update allowed origins
41+
### Platform-specific implementation
2142

22-
In addition to ensuring proper WebView settings, if you have allowed origins configured, it is essential to update the list to include:
43+
#### Android WebView
2344

24-
```txt
45+
```js wrap
46+
WebView webView = findViewById(R.id.webview);
47+
WebSettings webSettings = webView.getSettings();
2548

26-
challenges.cloudflare.com, about:blank, about:srcdoc
49+
// Required: Enable JavaScript
50+
webSettings.setJavaScriptEnabled(true);
2751

52+
// Required: Enable DOM storage
53+
webSettings.setDomStorageEnabled(true);
54+
55+
// Recommended: Enable other web features
56+
webSettings.setLoadWithOverviewMode(true);
57+
webSettings.setUseWideViewPort(true);
58+
webSettings.setAllowFileAccess(true);
59+
webSettings.setAllowContentAccess(true);
60+
61+
// Load your web content with Turnstile
62+
webView.loadUrl("https://yoursite.com/protected-form");
2863
```
2964

30-
:::note
65+
#### iOS WKWebView (Swift)
66+
67+
```js wrap
68+
import WebKit
69+
70+
class ViewController: UIViewController {
71+
@IBOutlet weak var webView: WKWebView!
72+
73+
override func viewDidLoad() {
74+
super.viewDidLoad()
75+
76+
// Configure WebView
77+
let configuration = WKWebViewConfiguration()
78+
configuration.preferences.javaScriptEnabled = true
79+
80+
// Load your web content with Turnstile
81+
if let url = URL(string: "https://yoursite.com/protected-form") {
82+
webView.load(URLRequest(url: url))
83+
}
84+
}
85+
}
86+
```
3187

32-
Only [React Native](https://github.com/react-native-webview/react-native-webview/blob/master/docs/Reference.md#originwhitelist) contains the allowed origins above by default.
33-
:::
88+
#### React Native WebView
89+
90+
```js wrap
91+
import { WebView } from 'react-native-webview';
92+
93+
export default function App() {
94+
return (
95+
<WebView
96+
source={{ uri: 'https://yoursite.com/protected-form' }}
97+
javaScriptEnabled={true}
98+
domStorageEnabled={true}
99+
allowsInlineMediaPlayback={true}
100+
mediaPlaybackRequiresUserAction={false}
101+
/>
102+
);
103+
}
104+
```
34105

35-
Without this, Turnstile challenges might fail to load. WebView should also be configured to allow insecure connections (`http` and `https`).
106+
#### Flutter WebView
107+
108+
```js wrap
109+
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
110+
111+
class WebViewScreen extends StatelessWidget {
112+
@override
113+
Widget build(BuildContext context) {
114+
return InAppWebView(
115+
initialUrlRequest: URLRequest(
116+
url: Uri.parse('https://yoursite.com/protected-form')
117+
),
118+
initialOptions: InAppWebViewGroupOptions(
119+
crossPlatform: InAppWebViewOptions(
120+
javaScriptEnabled: true,
121+
useShouldOverrideUrlLoading: false,
122+
),
123+
android: AndroidInAppWebViewOptions(
124+
domStorageEnabled: true,
125+
),
126+
ios: IOSInAppWebViewOptions(
127+
allowsInlineMediaPlayback: true,
128+
),
129+
),
130+
);
131+
}
132+
}
133+
```
36134

37-
## Maintain a consistent user agent
135+
---
38136

39-
When implementing Turnstile with WebViews, the user agent must stay consistent as changing the user agent will cause the challenges to fail.
137+
## Common implementation issues
40138

41-
## Use clearance cookies
139+
### User Agent consistency
42140

43-
When using [clearance cookies](/cloudflare-challenges/concepts/clearance/#pre-clearance-support-in-turnstile) with Turnstile, make sure that it is executed in the same environment where the challenges will occur, including the same browser and device configuration. The `cf_clearance` cookie will be only accepted in the same configured domain for Turnstile widget with the corresponding zone. Domains configured with the Turnstile widget must match the Cloudflare zone that issues [challenges](/cloudflare-challenges/).
141+
Changing the User Agent during a session causes Turnstile challenges to fail because the system relies on consistent browser characteristics to validate the visitor's authenticity. When the User Agent changes mid-session, Turnstile treats this as a potential security risk and rejects the challenge.
44142

45-
If pre-clearance is done in a different environment, the clearance cookie may become invalid and lead to more issued challenges.
143+
```js wrap
144+
// Android - Set consistent User Agent
145+
webSettings.setUserAgentString(webSettings.getUserAgentString());
146+
```
147+
```js wrap
148+
// iOS - Maintain default User Agent
149+
webView.customUserAgent = webView.value(forKey: "userAgent") as? String
150+
```
151+
152+
### Content Security Policy (CSP)
153+
154+
Strict [Content Security Policy](/turnstile/reference/content-security-policy/) settings can prevent Turnstile from loading the necessary scripts and making required network connections. This happens when CSP headers or meta tags block access to the domains and resources that Turnstile needs to function properly.
155+
156+
```html wrap
157+
<meta http-equiv="Content-Security-Policy" content="
158+
default-src 'self';
159+
script-src 'self' challenges.cloudflare.com 'unsafe-inline';
160+
connect-src 'self' challenges.cloudflare.com;
161+
frame-src 'self' challenges.cloudflare.com;
162+
">
163+
```
164+
165+
### Domain configuration
166+
167+
WebView security restrictions can prevent access to the domains that Turnstile requires for proper operation. Some WebViews are configured to only allow specific domains or block certain types of connections, which can interfere with Turnstile's ability to load challenges and communicate with Cloudflare's servers.
46168
47-
## Use Flutter with Turnstile
169+
To resolve this, configure your WebView's allowed origins to include all domains that Turnstile needs:
170+
171+
- `challenges.cloudflare.com`
172+
- `about:blank`
173+
- `about:srcdoc`
174+
- Your own domain(s)
175+
176+
The exact configuration method varies by platform, but the principle is to explicitly allow network access for these domains.
177+
178+
### Cookie and storage issues
179+
180+
Cookies and local storage not persisting between sessions can cause Turnstile to fail because it relies on these mechanisms to maintain state and track visitor behavior. This commonly occurs when WebView storage settings are too restrictive or when the app clears storage between sessions. Ensure that your WebView is configured to properly handle cookies and local storage.
181+
182+
```js wrap
183+
// Android - Enable cookies
184+
CookieManager.getInstance().setAcceptCookie(true);
185+
CookieManager.getInstance().setAcceptThirdPartyCookies(webView, true);
186+
```
48187

49-
For developers using [Flutter](https://pub.dev/packages/flutter_inappwebview), Turnstile is compatible and tested with Flutter's WebView implementation. Refer to the official Flutter WebView package for more details and usage.
188+
```js wrap
189+
// iOS - Configure cookie storage
190+
webView.configuration.websiteDataStore = WKWebsiteDataStore.default()
191+
```

0 commit comments

Comments
 (0)