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
<PackageReference Include="Persilsoft.Recaptcha.Blazor" Version="1.0.40" />
<PackageVersion Include="Persilsoft.Recaptcha.Blazor" Version="1.0.40" />
<PackageReference Include="Persilsoft.Recaptcha.Blazor" />
paket add Persilsoft.Recaptcha.Blazor --version 1.0.40
#r "nuget: Persilsoft.Recaptcha.Blazor, 1.0.40"
#:package Persilsoft.Recaptcha.Blazor@1.0.40
#addin nuget:?package=Persilsoft.Recaptcha.Blazor&version=1.0.40
#tool nuget:?package=Persilsoft.Recaptcha.Blazor&version=1.0.40
Persilsoft.Recaptcha.Blazor
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.Factoryprovides 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 fromExecuteAsync()
Returns: Result<bool, IEnumerable<string>> containing:
HasError-trueif verification failedSuccessValue-trueif verification succeededErrorValue- 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:
- Adjust
MinimumScorein backend configuration - Implement additional verification (email confirmation, phone verification)
- Block suspicious users
- Adjust
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:
- Check browser console for network errors
- Verify Site Key in
appsettings.json - Temporarily disable ad blockers
- Add your domain (including
localhostfor 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."
};
π¦ Related Packages
Required
- Persilsoft.Captcha.Factory - Server-side multi-provider verification orchestration
- Persilsoft.Recaptcha.Server - Google reCAPTCHA v3 server implementation
- Persilsoft.Result - Result pattern library (included as dependency)
Optional
- Persilsoft.Captcha.Contracts - Shared contracts for custom implementations
π Resources
- Google reCAPTCHA v3 Documentation
- Get reCAPTCHA Keys
- Blazor Documentation
- Persilsoft.Result Documentation
π 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 | Versions 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. |
-
net10.0
- Microsoft.AspNetCore.Components.Web (>= 10.0.0)
- Microsoft.Extensions.Http (>= 10.0.0)
- Persilsoft.Blazor.JSInterop (>= 1.0.14)
- Persilsoft.HttpDelegatingHandlers (>= 1.0.21)
- Persilsoft.Result (>= 1.0.6)
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 |