Turnstile là gì
Turnstile là một sản phẩm miễn phí từ Cloudflare, ra đời với mục tiêu trở thành một sự thay thế vượt trội cho Google reCAPTCHA. Cloudflare quảng cáo rằng Turnstile không chỉ mang lại trải nghiệm người dùng tốt hơn mà còn tăng cường bảo mật và đảm bảo quyền riêng tư dữ liệu.
Việc sử dụng Turnstile được phân chia thành hai gói chính:
Gói Miễn Phí (Free Tier)
- Giới hạn 10 widget.
- Bao gồm thương hiệu của Cloudflare trên widget.
- Chỉ sử dụng được "Chế độ được quản lý" (Managed Mode).
- Giới hạn 10 tên miền (hostname) cho mỗi widget.
Gói Trả Phí (Enterprise Plan)
- Gói này không có giá công khai và là một phần của gói Enterprise của Cloudflare, cung cấp các tính năng nâng cao:
- Không giới hạn số lượng widget.
- Không có thương hiệu Cloudflare (cho phép whitelabel).
- Nhiều chế độ khác nhau: "Managed", và cả "Chế độ không bao giờ tương tác" (Non-interactive mode).
- Không giới hạn tên miền.
Sơ đồ hoạt động
Đăng ký và lấy key từ Cloudflare
Truy cập Turnstile: Đăng nhập vào tài khoản Cloudflare của bạn. Trên thanh điều hướng bên trái, tìm và nhấp vào mục Turnstile.
Thêm Widget mới: Trong trang tổng quan của Turnstile, nhấn vào nút màu xanh + Add widget.
Điền thông tin Widget:
Widget name: Đặt tên widget
Hostname Management: Nhấp vào "Add Hostnames" và nhập tên miền của trang web bạn muốn bảo vệ. Để thử nghiệm trên máy local, bạn có thể nhập localhost.
Widget Mode: Trong phần này, hãy chọn "Managed". Đây là chế độ mặc định và thông minh nhất, Cloudflare sẽ tự quyết định khi nào cần hiển thị một thử thách tương tác cho người dùng.
Tạo và Lấy Keys
Nhấn nút "Create" ở cuối trang. Sau khi tạo thành công, Cloudflare sẽ cung cấp cho bạn hai chuỗi mã cực kỳ quan trọng:
🔑 Site Key: Dùng ở phía frontend (trong code HTML/JavaScript của bạn).
🔐 Secret Key: Dùng ở phía backend (trong code C#).
Hướng dẫn integration từ Cloudflare:
Server-Side Validation
Server của bạn sẽ thực hiện một POST request đến endpoint /siteverify của Cloudflare.
POST https://challenges.cloudflare.com/turnstile/v0/siteverify
Yêu cầu này phải chứa 2 thông tin chính:- secret: Chính là Secret Key mà Cloudflare đã cấp cho bạn.
- response: Là token mà widget Turnstile đã tạo ra ở phía client.
Cloudflare sẽ kiểm tra và trả về một kết quả dạng JSON.
Xác Thực Thành Công
Khi token hợp lệ, Cloudflare sẽ trả về một đối tượng JSON với success là true.
- success: true có nghĩa là người dùng đã pass validation.
- challenge_ts: Dấu thời gian (chuẩn ISO 8601) hostname
- action và cdata: Các dữ liệu tùy chỉnh bạn có thể truyền từ client.
- error-codes: Sẽ là một mảng rỗng khi thành công.
{
"success": true,
"error-codes": [],
"challenge_ts": "2025-08-09T00:07:23.274Z",
"hostname": "example.com",
"action": "login",
"cdata": "sessionid-123456789"
}
Xác thực thất bại
Khi có vấn đề, success sẽ là false và mảng error-codes sẽ cho bạn biết lý do.Error code | Description |
|---|---|
missing-input-secret | The secret parameter was not passed. |
invalid-input-secret | The secret parameter was invalid, did not exist, or is a testing secret key with a non-testing response. |
missing-input-response | The response parameter (token) was not passed. |
invalid-input-response | The response parameter (token) is invalid or has expired. Most of the time, this means a fake token has been used. If the error persists, contact customer support. |
bad-request | The request was rejected because it was malformed. |
timeout-or-duplicate | The response parameter (token) has already been validated before. This means that the token was issued five minutes ago and is no longer valid, or it was already redeemed. |
internal-error | An internal error happened while validating the response. The request can be retried. |
{
"success": false,
"error-codes": ["invalid-input-response"]
}
Ví dụ C# với Refit
Bạn có thể sử dụng Refit để tạo client gọi API xác thực:public interface ICloudflareTurnstileClient
{
[Post("/siteverify")]
[Headers("Content-Type: application/json")]
Task<CloudflareTurnstileVerifyResult> Verify(
CloudflareTurnstileVerifyRequestModel requestModel,
CancellationToken ct);
}
public record class CloudflareTurnstileVerifyResult(
[property: JsonPropertyName("success")] bool Success,
[property: JsonPropertyName("error-codes")] string[] ErrorCodes,
[property: JsonPropertyName("challenge_ts")] DateTimeOffset On,
[property: JsonPropertyName("hostname")] string Hostname
);
Bạn sẽ thấy lạ vì chỉ định nghĩa relative url tương đối: /siteverify. Refit không biết host (https://challenges.cloudflare.com/turnstile/v0) nằm ở đâu, nên bạn phải cấu hình BaseAddress khi đăng ký client.
Đăng ký ICloudflareTurnstileClient trong Program.cs
services.AddRefitClient<ICloudflareTurnstileClient>()
.ConfigureHttpClient(c => c.BaseAddress = new Uri(clientBaseUrl));
Chúng ta sẽ lấy relative url từ appsettings.json
{
"CloudflareTurnstile": {
"BaseUrl": "https://challenges.cloudflare.com/turnstile/v0",
"SiteKey": "0xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"SecretKey": "0xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}
}
File Program.cs
// Add Cloudflare Turnstile
builder.Services.AddCloudflareTurnstile(builder.Configuration.GetRequiredSection("CloudflareTurnstile"));
Tạo file CloudflareTurnstileRegistration
public static class CloudflareTurnstileRegistration
{
public static IServiceCollection AddCloudflareTurnstile(
this IServiceCollection services, IConfigurationSection configurationSection)
{
// configure
services.Configure<CloudflareTurnstileSettings>(configurationSection);
// read url required for refit
string? clientBaseUrl = configurationSection.GetValue<string>(nameof(CloudflareTurnstileSettings.BaseUrl));
if (string.IsNullOrWhiteSpace(clientBaseUrl))
{
throw new InvalidOperationException($"Cloudflare Turnstile {nameof(CloudflareTurnstileSettings.BaseUrl)} is required.");
}
// in this sample the provider can be a singleton
services.AddSingleton<CloudflareTurnstileProvider>();
// add client
services.AddRefitClient<ICloudflareTurnstileClient>()
.ConfigureHttpClient(c => c.BaseAddress = new Uri(clientBaseUrl));
// return
return services;
}
}
Khi Refit chạy, nó sẽ nối BaseAddress + relative path từ attribute để tạo URL đầy đủ:
https://challenges.cloudflare.com/turnstile/v0 + /siteverify
=> https://challenges.cloudflare.com/turnstile/v0/siteverify
Luồng gọi thực tế:
public class CaptchaService
{
private readonly ICloudflareTurnstileClient _client;
public CaptchaService(ICloudflareTurnstileClient client)
{
_client = client;
}
public async Task<bool> VerifyCaptchaAsync(string token)
{
var result = await _client.Verify(
new CloudflareTurnstileVerifyRequestModel("YOUR_SECRET_KEY", token),
CancellationToken.None);
return result.Success;
}
}
Tham khảo
ASP.NET Core Form protection with Cloudflare’s Turnstile
Github: BenjaminAbt / samples-aspnetcore-cloudflare-turnstile
Nhận xét
Đăng nhận xét