Edi.Captcha 5.3.0

dotnet add package Edi.Captcha --version 5.3.0
                    
NuGet\Install-Package Edi.Captcha -Version 5.3.0
                    
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="Edi.Captcha" Version="5.3.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Edi.Captcha" Version="5.3.0" />
                    
Directory.Packages.props
<PackageReference Include="Edi.Captcha" />
                    
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 Edi.Captcha --version 5.3.0
                    
#r "nuget: Edi.Captcha, 5.3.0"
                    
#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 Edi.Captcha@5.3.0
                    
#: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=Edi.Captcha&version=5.3.0
                    
Install as a Cake Addin
#tool nuget:?package=Edi.Captcha&version=5.3.0
                    
Install as a Cake Tool

Edi.Captcha.AspNetCore

The Captcha module used in my blog

.NET

NuGet

Install

NuGet Package Manager

Install-Package Edi.Captcha

or .NET CLI

dotnet add package Edi.Captcha

Session-Based Captcha (Traditional Approach)

1. Register in DI

services.AddSession(options =>
{
    options.IdleTimeout = TimeSpan.FromMinutes(20);
    options.Cookie.HttpOnly = true;
});

services.AddSessionBasedCaptcha();
// Don't forget to add this line in your `Configure` method.
app.UseSession();

or you can customize the options

services.AddSessionBasedCaptcha(option =>
{
    option.Letters = "2346789ABCDEFGHJKLMNPRTUVWXYZ";
    option.SessionName = "CaptchaCode";
    option.CodeLength = 4;
});

2. Generate Image

Using MVC Controller
private readonly ISessionBasedCaptcha _captcha;

public SomeController(ISessionBasedCaptcha captcha)
{
    _captcha = captcha;
}

[Route("get-captcha-image")]
public IActionResult GetCaptchaImage()
{
    var s = _captcha.GenerateCaptchaImageFileStream(
        HttpContext.Session,
        100,
        36
    );
    return s;
}
Using Middleware
app.UseSession().UseSessionCaptcha(options =>
{
    options.RequestPath = "/captcha-image";
    options.ImageHeight = 36;
    options.ImageWidth = 100;
});

3. Add CaptchaCode Property to Model

[Required]
[StringLength(4)]
public string CaptchaCode { get; set; }

5. View

<div class="col">
    <div class="input-group">
        <div class="input-group-prepend">
            <img id="img-captcha" src="~/captcha-image" />
        </div>
        <input type="text" 
               asp-for="CommentPostModel.CaptchaCode" 
               class="form-control" 
               placeholder="Captcha Code" 
               autocomplete="off" 
               minlength="4"
               maxlength="4" />
    </div>
    <span asp-validation-for="CommentPostModel.CaptchaCode" class="text-danger"></span>
</div>

6. Validate Input

_captcha.ValidateCaptchaCode(model.CommentPostModel.CaptchaCode, HttpContext.Session)

To make your code look more cool, you can also write an Action Filter like this:

public class ValidateCaptcha : ActionFilterAttribute
{
    private readonly ISessionBasedCaptcha _captcha;

    public ValidateCaptcha(ISessionBasedCaptcha captcha)
    {
        _captcha = captcha;
    }

    public override void OnActionExecuting(ActionExecutingContext context)
    {
        var captchaedModel =
            context.ActionArguments.Where(p => p.Value is ICaptchable)
                                   .Select(x => x.Value as ICaptchable)
                                   .FirstOrDefault();

        if (null == captchaedModel)
        {
            context.ModelState.AddModelError(nameof(captchaedModel.CaptchaCode), "Captcha Code is required");
            context.Result = new BadRequestObjectResult(context.ModelState);
        }
        else
        {
            if (!_captcha.Validate(captchaedModel.CaptchaCode, context.HttpContext.Session))
            {
                context.ModelState.AddModelError(nameof(captchaedModel.CaptchaCode), "Wrong Captcha Code");
                context.Result = new ConflictObjectResult(context.ModelState);
            }
            else
            {
                base.OnActionExecuting(context);
            }
        }
    }
}

and then

services.AddScoped<ValidateCaptcha>();

and then


public class YourModelWithCaptchaCode : ICaptchable
{
    public string YourProperty { get; set; }

    [Required]
    [StringLength(4)]
    public string CaptchaCode { get; set; }
}

[ServiceFilter(typeof(ValidateCaptcha))]
public async Task<IActionResult> SomeAction(YourModelWithCaptchaCode model)
{
    // ....
}

Advantages of Stateless Captcha:

  • ✅ Works in clustered/load-balanced environments
  • ✅ No server-side session storage required
  • ✅ Built-in expiration through encryption
  • ✅ Secure token-based validation
  • ✅ Better scalability
  • ✅ Single API call for both token and image

1. Register in DI

services.AddStatelessCaptcha();

or with custom options:

services.AddStatelessCaptcha(options =>
{
    options.Letters = "2346789ABCDGHKMNPRUVWXYZ";
    options.CodeLength = 4;
    options.TokenExpiration = TimeSpan.FromMinutes(5);
});

2. Create Model with Token Support

public class StatelessHomeModel
{
    [Required]
    [StringLength(4)]
    public string CaptchaCode { get; set; }
    
    public string CaptchaToken { get; set; }
}

3. Example Controller and View

See: src\Edi.Captcha.SampleApp\Controllers\StatelessController.cs and src\Edi.Captcha.SampleApp\Views\Stateless\Index.cshtml for a complete example.

Cluster/Load Balancer Configuration

⚠️ Important for Production Deployments: The stateless captcha uses ASP.NET Core's Data Protection API for token encryption. In clustered environments or behind load balancers, you must configure shared data protection keys to ensure captcha tokens can be validated on any server.

Option 1: File System (Network Share)
public void ConfigureServices(IServiceCollection services)
{
    services.AddDataProtection()
        .PersistKeysToFileSystem(new DirectoryInfo(@"\\shared-network-path\keys"))
        .SetApplicationName("YourAppName"); // Must be consistent across all instances
    
    services.AddStatelessCaptcha(options =>
    {
        // Your captcha configuration
    });
}
Option 2: Azure Blob Storage
public void ConfigureServices(IServiceCollection services)
{
    services.AddDataProtection()
        .PersistKeysToAzureBlobStorage("DefaultEndpointsProtocol=https;AccountName=...", "keys-container", "dataprotection-keys.xml")
        .SetApplicationName("YourAppName");
    
    services.AddStatelessCaptcha(options =>
    {
        // Your captcha configuration
    });
}
Option 3: Redis
public void ConfigureServices(IServiceCollection services)
{
    services.AddDataProtection()
        .PersistKeysToStackExchangeRedis(ConnectionMultiplexer.Connect("your-redis-connection"), "DataProtection-Keys")
        .SetApplicationName("YourAppName");
    
    services.AddStatelessCaptcha(options =>
    {
        // Your captcha configuration
    });
}
Option 4: SQL Server
public void ConfigureServices(IServiceCollection services)
{
    services.AddDataProtection()
        .PersistKeysToDbContext<YourDbContext>()
        .SetApplicationName("YourAppName");
    
    services.AddStatelessCaptcha(options =>
    {
        // Your captcha configuration
    });
}
Single Server Deployment

For single server deployments, no additional configuration is required. The default Data Protection configuration will work correctly.

Testing Cluster Configuration

To verify your cluster configuration is working:

  1. Generate a captcha on Server A
  2. Submit the form to Server B (or any other server)
  3. Validation should succeed

If validation fails with properly entered captcha codes, check your Data Protection configuration.

When to use Shared Key Stateless Captcha:

  • ✅ Full control over encryption keys
  • ✅ Works without ASP.NET Core Data Protection API
  • ✅ Simpler cluster configuration
  • ✅ Custom key rotation strategies
  • ✅ Works across different application frameworks
  • ✅ No dependency on external storage for keys

1. Register in DI with Shared Key

services.AddSharedKeyStatelessCaptcha(options =>
{
    options.SharedKey = "your-32-byte-base64-encoded-key"; // Generate securely
    options.FontStyle = FontStyle.Bold;
    options.DrawLines = true;
    options.TokenExpiration = TimeSpan.FromMinutes(5);
});

2. Generate Secure Shared Key

Important: Use a cryptographically secure random key. Here's how to generate one:

// Generate a secure 256-bit key (one-time setup)
using (var rng = RandomNumberGenerator.Create())
{
    var keyBytes = new byte[32]; // 256 bits
    rng.GetBytes(keyBytes);
    var base64Key = Convert.ToBase64String(keyBytes);
    Console.WriteLine($"Shared Key: {base64Key}");
}

3. Configuration Options

Configuration File (appsettings.json)
{
  "CaptchaSettings": {
    "SharedKey": "your-generated-base64-key-here",
    "TokenExpirationMinutes": 5
  }
}
public void ConfigureServices(IServiceCollection services)
{
    var captchaKey = Configuration["CaptchaSettings:SharedKey"];
    var expirationMinutes = Configuration.GetValue<int>("CaptchaSettings:TokenExpirationMinutes", 5);
    
    services.AddSharedKeyStatelessCaptcha(options =>
    {
        options.SharedKey = captchaKey;
        options.TokenExpiration = TimeSpan.FromMinutes(expirationMinutes);
        // Other options...
    });
}

4. Example Controller and View

See: src\Edi.Captcha.SampleApp\Controllers\SharedKeyStatelessController.cs and src\Edi.Captcha.SampleApp\Views\SharedKeyStateless\Index.cshtml for a complete example.

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  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 (2)

Showing the top 2 popular GitHub repositories that depend on Edi.Captcha:

Repository Stars
EdiWang/Moonglade
Blog system of https://edi.wang, runs on Microsoft Azure
AiursoftWeb/Infrastructures
Mirror of: https://gitlab.aiursoft.com/aiursoft/infrastructures
Version Downloads Last Updated
5.3.0 37 11/13/2025
5.2.1 181 10/31/2025
5.2.0 204 10/14/2025
5.1.1 144 10/4/2025
5.1.0 104 10/3/2025
5.0.1 109 10/3/2025
5.0.0 220 10/3/2025
4.0.0 381 8/17/2025
3.26.4 299 7/31/2025
3.26.3 321 7/2/2025
3.26.2 383 6/6/2025
3.26.1 1,077 3/11/2025
3.26.0 409 2/8/2025
3.25.0 1,532 11/13/2024
3.24.0 1,478 8/11/2024
3.23.1 724 7/14/2024
3.23.0 264 7/8/2024
3.22.0 1,078 5/19/2024
3.21.2 1,409 3/11/2024
3.21.1 1,363 1/29/2024
3.21.0 982 12/15/2023
3.20.0 406 12/11/2023
3.19.1 1,446 11/6/2023
3.19.0 616 10/11/2023
3.18.0 988 9/16/2023
3.17.0 691 8/29/2023
3.16.0 952 7/13/2023
3.15.0 4,277 1/5/2023
3.14.0 1,450 11/9/2022
3.13.1 4,004 9/14/2022
3.13.0 4,242 7/31/2022
3.12.0 542 7/31/2022
3.11.0 1,869 6/12/2022
3.10.0 5,025 2/9/2022
3.9.0 696 1/10/2022
3.8.0 425 1/9/2022
3.7.0 581 12/9/2021
3.6.1 1,616 12/8/2021
3.6.0 663 11/13/2021
3.5.0 1,346 11/9/2021
3.4.0 483 11/9/2021
3.3.0 2,192 6/7/2021
3.2.0 1,596 4/1/2021
3.1.0 591 3/31/2021
3.0.1 3,151 11/27/2020
3.0.0 825 11/11/2020
2.2.0 4,171 12/4/2019
2.1.0 824 11/22/2019
2.0.0 1,197 9/24/2019
2.0.0-preview3 426 9/17/2019
2.0.0-preview2 450 9/15/2019
2.0.0-preview 424 9/11/2019
1.3.1 1,616 5/1/2019
1.3.0 878 4/11/2019
1.2.0 1,049 1/30/2019
1.1.0 920 12/13/2018
1.0.0 1,628 11/11/2018