Persilsoft.Recaptcha.Blazor 1.0.40

dotnet add package Persilsoft.Recaptcha.Blazor --version 1.0.40
                    
NuGet\Install-Package Persilsoft.Recaptcha.Blazor -Version 1.0.40
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Persilsoft.Recaptcha.Blazor" Version="1.0.40" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Persilsoft.Recaptcha.Blazor" Version="1.0.40" />
                    
Directory.Packages.props
<PackageReference Include="Persilsoft.Recaptcha.Blazor" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Persilsoft.Recaptcha.Blazor --version 1.0.40
                    
#r "nuget: Persilsoft.Recaptcha.Blazor, 1.0.40"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Persilsoft.Recaptcha.Blazor@1.0.40
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Persilsoft.Recaptcha.Blazor&version=1.0.40
                    
Install as a Cake Addin
#tool nuget:?package=Persilsoft.Recaptcha.Blazor&version=1.0.40
                    
Install as a Cake Tool

Persilsoft.Recaptcha.Blazor

NuGet License

A modern Blazor WebAssembly component library for Google reCAPTCHA v3 integration with clean architecture and built-in verification support using the Result pattern.


✨ Features

  • ReCAPTCHA v3 Integration - Invisible, no user interaction required
  • Built-in Verification - Component handles both token generation and validation
  • Result Pattern - Returns Result<bool, IEnumerable<string>> for elegant error handling
  • Clean Architecture - Well-structured, maintainable, testable code
  • Type-Safe - Full C# nullable reference types support
  • Detailed Error Codes - Specific error codes for different failure scenarios
  • Functional API - Support for both imperative and functional programming styles
  • Multiple Instances - Support for multiple components on the same page

πŸ“¦ Installation

Client (Blazor WebAssembly)

dotnet add package Persilsoft.Recaptcha.Blazor

Server (ASP.NET Core Web API)

dotnet add package Persilsoft.Captcha.Factory

Note: Persilsoft.Captcha.Factory provides the verification endpoint automatically.


βš™οΈ Configuration

Client Setup

1. Register services in Program.cs:

using Persilsoft.Recaptcha.Blazor;
using Persilsoft.Recaptcha.Blazor.Options;

var builder = WebAssemblyHostBuilder.CreateDefault(args);

builder.Services.AddRecaptchaServices(
    options => builder.Configuration
        .GetSection(RecaptchaOptions.SectionKey)
        .Bind(options));

await builder.Build().RunAsync();

2. Configure appsettings.json:

{
  "RecaptchaOptions": {
    "SiteKey": "your-recaptcha-site-key",
    "WebApiBaseAddress": "https://your-api-domain.com"
  }
}

Server Setup

1. Register services and endpoint in Program.cs:

using Persilsoft.Captcha.Factory;

var builder = WebApplication.CreateBuilder(args);

// Register services
builder.Services.AddCaptcha(builder.Configuration);

var app = builder.Build();

// Register endpoint (POST /captcha/verify)
app.MapCaptchaVerifyEndpoint();

app.Run();

2. Configure appsettings.json:

{
  "CaptchaOptions": {
    "DefaultProvider": "recaptcha",
    "Recaptcha": {
      "SecretKey": "your-recaptcha-secret-key",
      "VerifyEndpoint": "https://www.google.com/recaptcha/api/siteverify",
      "MinimumScore": 0.5
    }
  }
}

πŸš€ Usage

Basic Example (Imperative Style)

@page "/contact"
@using Persilsoft.Recaptcha.Blazor.Components
@using Persilsoft.Result

<EditForm Model="Model" OnValidSubmit="HandleSubmitAsync">
    <DataAnnotationsValidator />
    
    <div class="mb-3">
        <label>Name</label>
        <InputText @bind-Value="Model.Name" class="form-control" />
        <ValidationMessage For="@(() => Model.Name)" />
    </div>

    <div class="mb-3">
        <label>Email</label>
        <InputText @bind-Value="Model.Email" class="form-control" />
        <ValidationMessage For="@(() => Model.Email)" />
    </div>

    
    <RecaptchaComponent @ref="_recaptcha" 
                        Action="contact_form"
                        OnError="HandleError" />

    @if (!string.IsNullOrEmpty(_errorMessage))
    {
        <div class="alert alert-danger">@_errorMessage</div>
    }

    <button type="submit" class="btn btn-primary" disabled="@_isSubmitting">
        @(_isSubmitting ? "Submitting..." : "Submit")
    </button>
</EditForm>

@code {
    private RecaptchaComponent _recaptcha = default!;
    private bool _isSubmitting;
    private string? _errorMessage;
    private ContactModel Model { get; set; } = new();

    private async Task HandleSubmitAsync()
    {
        _isSubmitting = true;
        _errorMessage = null;
        
        try
        {
            // Step 1: Get reCAPTCHA token
            var token = await _recaptcha.ExecuteAsync();
            if (string.IsNullOrEmpty(token))
            {
                _errorMessage = "Failed to obtain reCAPTCHA token";
                return;
            }

            // Step 2: Verify token with Result pattern
            var result = await _recaptcha.VerifyAsync(token);
            
            // Step 3: Check for errors
            if (result.HasError)
            {
                var errors = result.ErrorValue!;
                _errorMessage = GetFriendlyErrorMessage(errors.FirstOrDefault());
                return;
            }

            // Step 4: Process form
            await SubmitFormAsync();
        }
        finally
        {
            _isSubmitting = false;
        }
    }

    private void HandleError(string error)
    {
        _errorMessage = error;
        StateHasChanged();
    }

    private string GetFriendlyErrorMessage(string? errorCode)
    {
        return errorCode switch
        {
            "missing-token" => "Captcha token is missing. Please try again.",
            "invalid-token" => "Captcha token is invalid. Please try again.",
            _ when errorCode?.StartsWith("low-score:") == true => "Security verification failed. Please try again.",
            "network-error" => "Network error. Please check your connection.",
            "timeout-error" => "Verification timed out. Please try again.",
            _ => "Security verification failed. Please try again."
        };
    }

    private async Task SubmitFormAsync()
    {
        // Your form submission logic
        await Task.Delay(1000);
    }

    public class ContactModel
    {
        [Required]
        public string Name { get; set; } = string.Empty;
        
        [Required, EmailAddress]
        public string Email { get; set; } = string.Empty;
    }
}

Functional Style with HandleError

@code {
    private async Task HandleSubmitAsync()
    {
        _isSubmitting = true;
        _errorMessage = null;
        
        try
        {
            var token = await _recaptcha.ExecuteAsync();
            if (string.IsNullOrEmpty(token))
            {
                _errorMessage = "Failed to obtain token";
                return;
            }

            var result = await _recaptcha.VerifyAsync(token);
            
            // Functional approach with HandleError
            result.HandleError(
                onSuccess: async _ =>
                {
                    await SubmitFormAsync();
                    _errorMessage = null;
                },
                onError: errors =>
                {
                    var primaryError = errors.FirstOrDefault();
                    _errorMessage = GetFriendlyErrorMessage(primaryError);
                }
            );
        }
        finally
        {
            _isSubmitting = false;
        }
    }
}

Login Example

@page "/login"
@using Persilsoft.Recaptcha.Blazor.Components
@using Persilsoft.Result

<EditForm Model="Model" OnValidSubmit="HandleLoginAsync">
    <InputText @bind-Value="Model.Email" placeholder="Email" class="form-control mb-3" />
    <InputText @bind-Value="Model.Password" type="password" placeholder="Password" class="form-control mb-3" />
    
    <RecaptchaComponent @ref="_recaptcha" Action="login" OnError="HandleError" />
    
    @if (!string.IsNullOrEmpty(_errorMessage))
    {
        <div class="alert alert-danger">@_errorMessage</div>
    }
    
    <button type="submit" class="btn btn-primary w-100">Login</button>
</EditForm>

@code {
    private RecaptchaComponent _recaptcha = default!;
    private string? _errorMessage;
    private LoginModel Model { get; set; } = new();

    private async Task HandleLoginAsync()
    {
        var token = await _recaptcha.ExecuteAsync();
        if (string.IsNullOrEmpty(token)) return;
        
        var result = await _recaptcha.VerifyAsync(token);
        
        if (result.HasError)
        {
            _errorMessage = "Security verification failed";
            return;
        }
        
        await AuthService.LoginAsync(Model);
    }

    private void HandleError(string error)
    {
        _errorMessage = $"reCAPTCHA error: {error}";
    }
}

Advanced: Retry Logic

@code {
    private async Task<Result<bool, IEnumerable<string>>> VerifyWithRetryAsync(
        string token, 
        int maxRetries = 3)
    {
        for (int attempt = 1; attempt <= maxRetries; attempt++)
        {
            var result = await _recaptcha.VerifyAsync(token);

            if (!result.HasError)
                return result;

            var primaryError = result.ErrorValue!.FirstOrDefault();
            
            // Retry only for transient errors
            var shouldRetry = primaryError is "network-error" or "timeout-error";

            if (!shouldRetry || attempt == maxRetries)
                return result;

            // Exponential backoff
            await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, attempt - 1)));
        }

        return new Result<bool, IEnumerable<string>>(new[] { "max-retries-exceeded" });
    }
}

πŸ“š API Reference

RecaptchaComponent

Parameters
Parameter Type Default Description
Action string "submit" Action name for reCAPTCHA analytics (e.g., "login", "register", "contact")
OnError EventCallback<string> - Callback invoked when errors occur during initialization or execution
Methods
ExecuteAsync()

Executes reCAPTCHA v3 and returns a token.

Task<string?> ExecuteAsync()

Returns: reCAPTCHA token (valid for 2 minutes) or null if failed.

Example:

var token = await _recaptcha.ExecuteAsync();
if (!string.IsNullOrEmpty(token))
{
    // Proceed with verification
}
VerifyAsync(string token)

Verifies the token with your backend using the Result pattern.

Task<Result<bool, IEnumerable<string>>> VerifyAsync(string token)

Parameters:

  • token - Token obtained from ExecuteAsync()

Returns: Result<bool, IEnumerable<string>> containing:

  • HasError - true if verification failed
  • SuccessValue - true if verification succeeded
  • ErrorValue - Collection of error codes if verification failed

Example:

var result = await _recaptcha.VerifyAsync(token);

if (result.HasError)
{
    var errors = result.ErrorValue!;
    Console.WriteLine($"Errors: {string.Join(", ", errors)}");
}
else
{
    Console.WriteLine("Verification successful!");
}

πŸ” Error Codes

The VerifyAsync() method returns detailed error codes in Result.ErrorValue:

Error Code Description Recommended Action
missing-token Token is null or empty Reload page or call ExecuteAsync() again
invalid-token Token is invalid or expired Execute captcha again
low-score:X.XX reCAPTCHA score below threshold (e.g., low-score:0.3) Show additional verification or block action
network-error Network communication error Retry automatically or ask user to check connection
timeout-error Verification request timed out Retry with exponential backoff
configuration-error Backend configuration issue Contact system administrator
http-error-XXX HTTP error from provider (e.g., http-error-502) Retry or show service unavailable message

Example of handling specific errors:

var result = await _recaptcha.VerifyAsync(token);

if (result.HasError)
{
    var primaryError = result.ErrorValue!.FirstOrDefault();
    
    var message = primaryError switch
    {
        _ when primaryError.StartsWith("low-score:") => 
            "Security check failed. Please try again or contact support.",
        "network-error" or "timeout-error" => 
            "Connection error. Retrying...",
        "invalid-token" => 
            "Verification expired. Please try again.",
        _ => 
            "Verification failed. Please try again."
    };
    
    ShowError(message);
}

πŸ”§ How It Works

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚              Blazor WebAssembly (Client)                    β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  1. ExecuteAsync() β†’ Get token from Google                  β”‚
β”‚  2. VerifyAsync(token) β†’ POST /captcha/verify               β”‚
β”‚  3. Receive Result<bool, IEnumerable<string>>               β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                   β”‚
                   β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚              ASP.NET Core API (Server)                      β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  3. Validate token with Google                              β”‚
β”‚  4. Return Result<bool, IEnumerable<string>> to client      β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

πŸ› Troubleshooting

"ReCaptcha no estΓ‘ inicializado"

Cause: Component not initialized before calling ExecuteAsync().
Solution: Ensure component has rendered. Add small delay if needed:

await Task.Delay(100);
var token = await _recaptcha.ExecuteAsync();

Verification Returns Errors

Common error codes and solutions:

invalid-token or Token Expired
  • Cause: Token is older than 2 minutes
  • Solution: Generate a new token with ExecuteAsync() immediately before verification
low-score:X.XX
  • Cause: reCAPTCHA score below configured threshold (default: 0.5)
  • Solutions:
    1. Adjust MinimumScore in backend configuration
    2. Implement additional verification (email confirmation, phone verification)
    3. Block suspicious users
network-error or timeout-error
  • Cause: Network connectivity issues or slow backend
  • Solution: Implement retry logic (see Advanced examples)
configuration-error
  • Common Causes:
    • Mismatched keys (Site Key on client vs Secret Key on server)
    • Missing Secret Key in backend configuration
    • Wrong domain in Google reCAPTCHA Admin Console
  • Solution: Verify configuration in both client and server

CORS Issues

If verification fails with network errors, configure CORS in your backend:

builder.Services.AddCors(options =>
{
    options.AddPolicy("AllowClient", policy =>
        policy.WithOrigins("https://your-domain.com")
              .AllowAnyMethod()
              .AllowAnyHeader());
});

app.UseCors("AllowClient");

"Failed to load ReCaptcha script"

Causes:

  • Network error or blocked domain
  • Incorrect Site Key
  • Ad blocker interference

Solutions:

  1. Check browser console for network errors
  2. Verify Site Key in appsettings.json
  3. Temporarily disable ad blockers
  4. Add your domain (including localhost for development) to authorized domains in Google reCAPTCHA Admin Console

🎯 Best Practices

1. Always Check for Errors

var result = await _recaptcha.VerifyAsync(token);

if (result.HasError)
{
    // Handle error - don't proceed
    return;
}

// Proceed with business logic

2. Use Specific Action Names


<RecaptchaComponent Action="login" />


<RecaptchaComponent Action="register" />


<RecaptchaComponent Action="contact_form" />

This helps with analytics in Google reCAPTCHA Admin Console.

3. Implement Retry for Transient Errors

var transientErrors = new[] { "network-error", "timeout-error" };
var primaryError = result.ErrorValue?.FirstOrDefault();

if (transientErrors.Contains(primaryError))
{
    await Task.Delay(2000);
    result = await _recaptcha.VerifyAsync(token);
}

4. Show User-Friendly Messages

Don't expose technical error codes to users:

private string GetUserMessage(string? errorCode) => errorCode switch
{
    "missing-token" or "invalid-token" => "Please try again.",
    _ when errorCode?.StartsWith("low-score:") == true => "Verification failed. Please contact support if this persists.",
    "network-error" => "Please check your internet connection.",
    _ => "An error occurred. Please try again."
};

Required

Optional


πŸ”— Resources


πŸ“„ License

Copyright Β© 2025 Persilsoft. All rights reserved.

Licensed under the MIT License.


πŸ“§ Support

For issues or questions, please open an issue on the GitHub repository.


Made with ❀️ by Edinson Aldaz

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.40 151 12/13/2025
1.0.39 602 12/1/2025
1.0.38 414 5/2/2025
1.0.37 309 4/13/2025
1.0.36 208 4/13/2025
1.0.35 250 4/12/2025
1.0.34 455 3/20/2025
1.0.33 246 3/20/2025
1.0.32 250 3/19/2025
1.0.31 807 10/16/2024
1.0.30 186 10/16/2024
1.0.29 157 10/15/2024
1.0.28 224 10/14/2024
1.0.27 218 10/6/2024
1.0.26 212 10/4/2024
1.0.25 205 10/4/2024
1.0.24 218 9/1/2024
1.0.23 222 8/28/2024
1.0.22 183 8/28/2024
1.0.21 194 8/18/2024
Loading failed