Chuyển đến nội dung chính

Bảo Vệ Form ASP.NET Core với Cloudflare Turnstile

Bài viết này mình chỉ bổ sung 1 số ý quan trọng để hiểu thêm về Cloudflare Turnstile từ link:  ASP.NET Core Form protection with Cloudflare’s Turnstile

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

sequenceDiagram participant User participant Browser as Web Browser (Client) participant YourServer as ASP.NET Server (Backend) participant Cloudflare as Cloudflare API %% Step 1: Load page and get Turnstile token rect rgb(230, 245, 255) User->>+Browser: Load page with form Browser->>+Cloudflare: Request Turnstile challenge Cloudflare-->>-Browser: Run challenge & return token Note right of Browser: Token is inserted into the form end %% Step 2: Submit form with token rect rgb(240, 255, 240) User->>Browser: Fill in info and submit form Browser->>+YourServer: Send form data + token end %% Step 3: Server verifies token with Cloudflare rect rgb(255, 240, 230) YourServer->>+Cloudflare: Send (Secret Key + Token) for verification Cloudflare-->>-YourServer: Return result { success: true/false } end %% Step 4: Result handling alt Verification successful (success: true) YourServer-->>Browser: Process logic (e.g., login success) Browser-->>User: Return success status else Verification failed (success: false) YourServer-->>Browser: Skip logic, return error Browser-->>User: Return error status end

Đă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:

Client side integration code

Server side integration code

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. 
VD
{
  "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-secretThe secret parameter was not passed.
invalid-input-secretThe secret parameter was invalid, did not exist, or is a testing secret key with a non-testing response.
missing-input-responseThe response parameter (token) was not passed.
invalid-input-responseThe 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-requestThe request was rejected because it was malformed.
timeout-or-duplicateThe 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-errorAn internal error happened while validating the response. The request can be retried.
VD:
{
  "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

Bài đăng phổ biến từ blog này

[ASP.NET MVC] Authentication và Authorize

Một trong những vấn đề bảo mật cơ bản nhất là đảm bảo những người dùng hợp lệ truy cập vào hệ thống. ASP.NET đưa ra 2 khái niệm: Authentication và Authorize Authentication xác nhận bạn là ai. Ví dụ: Bạn có thể đăng nhập vào hệ thống bằng username và password hoặc bằng ssh. Authorization xác nhận những gì bạn có thể làm. Ví dụ: Bạn được phép truy cập vào website, đăng thông tin lên diễn đàn nhưng bạn không được phép truy cập vào trang mod và admin.

ASP.NET MVC: Cơ bản về Validation

Validation (chứng thực) là một tính năng quan trọng trong ASP.NET MVC và được phát triển trong một thời gian dài. Validation vắng mặt trong phiên bản đầu tiên của asp.net mvc và thật khó để tích hợp 1 framework validation của một bên thứ 3 vì không có khả năng mở rộng. ASP.NET MVC2 đã hỗ trợ framework validation do Microsoft phát triển, tên là Data Annotations. Và trong phiên bản 3, framework validation đã hỗ trợ tốt hơn việc xác thực phía máy khách, và đây là một xu hướng của việc phát triển ứng dụng web ngày nay.

Tổng hợp một số kiến thức lập trình về Amibroker

Giới thiệu về Amibroker Amibroker theo developer Tomasz Janeczko được xây dựng dựa trên ngôn ngữ C. Vì vậy bộ code Amibroker Formula Language sử dụng có syntax khá tương đồng với C, ví dụ như câu lệnh #include để import hay cách gói các object, hàm trong các block {} và kết thúc câu lệnh bằng dấu “;”. AFL trong Amibroker là ngôn ngữ xử lý mảng (an array processing language). Nó hoạt động dựa trên các mảng (các dòng/vector) số liệu, khá giống với cách hoạt động của spreadsheet trên excel.