Edi.Captcha 5.0.1
Edi.Captcha.AspNetCore
The Captcha module used in my blog
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().UseCaptchaImage(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)
{
// ....
}
Stateless Captcha (Recommended for Scalable Applications)
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
using Edi.Captcha.SampleApp.Models;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Diagnostics;
namespace Edi.Captcha.SampleApp.Controllers;
public class StatelessController(IStatelessCaptcha captcha) : Controller
{
public IActionResult Index()
{
return View(new StatelessHomeModel());
}
[HttpPost]
public IActionResult Index(StatelessHomeModel model)
{
if (ModelState.IsValid)
{
bool isValidCaptcha = captcha.Validate(model.CaptchaCode, model.CaptchaToken);
return Content(isValidCaptcha ? "Success - Stateless captcha validated!" : "Invalid captcha code");
}
return BadRequest();
}
[Route("get-stateless-captcha")]
public IActionResult GetStatelessCaptcha()
{
var result = captcha.GenerateCaptcha(100, 36);
return Json(new {
token = result.Token,
imageBase64 = Convert.ToBase64String(result.ImageBytes)
});
}
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}
}
4. Example View
@model StatelessHomeModel
@{
ViewData["Title"] = "Stateless Captcha Example";
}
<div class="text-center">
<h1 class="display-4">Stateless Captcha Example</h1>
<p>This example shows how to use stateless captcha that works in clustered environments.</p>
</div>
<div class="row">
<div class="col-md-6 offset-md-3">
<div class="card">
<div class="card-header">
<h5>Stateless Captcha Form</h5>
</div>
<div class="card-body">
<form asp-action="Index" method="post" id="stateless-form">
<div class="form-group mb-3">
<label>Captcha Image:</label>
<div class="d-flex align-items-center">
<img id="captcha-image" src="" alt="Captcha" class="me-2" style="border: 1px solid #ccc;" />
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="refreshCaptcha()">
🔄 Refresh
</button>
</div>
<small class="form-text text-muted">Click refresh to get a new captcha</small>
</div>
<div class="form-group mb-3">
<label asp-for="CaptchaCode">Enter Captcha Code:</label>
<input asp-for="CaptchaCode" class="form-control" placeholder="Enter the code from image" autocomplete="off" />
<span asp-validation-for="CaptchaCode" class="text-danger"></span>
</div>
<input type="hidden" asp-for="CaptchaToken" id="captcha-token" />
<div class="form-group">
<button type="submit" class="btn btn-primary">Submit</button>
<a asp-controller="Home" asp-action="Index" class="btn btn-secondary">Session-based Example</a>
</div>
</form>
<div class="mt-4">
<h6>Advantages of Stateless Captcha:</h6>
<ul class="small">
<li>✅ Works in clustered/load-balanced environments</li>
<li>✅ No server-side session storage required</li>
<li>✅ Built-in expiration through encryption</li>
<li>✅ Secure token-based validation</li>
<li>✅ Better scalability</li>
<li>✅ Single API call for both token and image</li>
</ul>
</div>
</div>
</div>
</div>
</div>
<script>
async function refreshCaptcha() {
try {
const response = await fetch('/get-stateless-captcha');
const data = await response.json();
// Set the token for validation
document.getElementById('captcha-token').value = data.token;
// Set the image source using base64 data
document.getElementById('captcha-image').src = `data:image/png;base64,${data.imageBase64}`;
// Clear the input
document.getElementById('CaptchaCode').value = '';
} catch (error) {
console.error('Error refreshing captcha:', error);
alert('Failed to load captcha. Please try again.');
}
}
// Initialize captcha on page load
document.addEventListener('DOMContentLoaded', function() {
refreshCaptcha();
});
</script>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
Showing the top 20 packages that depend on Edi.Captcha.
| Packages |
|---|
|
MoongladePure.Comments
Package Description
|
.NET 8.0
- SixLabors.ImageSharp (>= 3.1.11)
- SixLabors.ImageSharp.Drawing (>= 2.1.7)
.NET 9.0
- SixLabors.ImageSharp (>= 3.1.11)
- SixLabors.ImageSharp.Drawing (>= 2.1.7)
| Version | Last updated |
|---|---|
| 5.3.0 | 11/14/2025 |
| 5.2.1 | 10/31/2025 |
| 5.2.0 | 10/15/2025 |
| 5.1.1 | 10/4/2025 |
| 5.1.0 | 10/8/2025 |
| 5.0.1 | 10/8/2025 |
| 5.0.0 | 10/8/2025 |
| 4.0.0 | 8/18/2025 |
| 3.26.4 | 7/31/2025 |
| 3.26.3 | 7/3/2025 |
| 3.26.2 | 6/7/2025 |
| 3.26.1 | 3/12/2025 |
| 3.26.0 | 4/2/2025 |
| 3.25.0 | 3/13/2025 |
| 3.24.0 | 4/30/2025 |
| 3.23.1 | 4/27/2025 |
| 3.23.0 | 5/7/2025 |
| 3.22.0 | 6/7/2025 |
| 3.21.2 | 5/13/2025 |
| 3.21.1 | 5/12/2025 |
| 3.21.0 | 6/7/2025 |
| 3.20.0 | 5/1/2025 |
| 3.19.1 | 4/25/2025 |
| 3.19.0 | 3/28/2025 |
| 3.18.0 | 5/12/2025 |
| 3.17.0 | 6/30/2025 |
| 3.16.0 | 4/28/2025 |
| 3.15.0 | 6/30/2025 |
| 3.14.0 | 5/5/2025 |
| 3.13.1 | 5/6/2025 |
| 3.13.0 | 5/9/2025 |
| 3.12.0 | 5/3/2025 |
| 3.11.0 | 6/6/2025 |
| 3.10.0 | 4/18/2025 |
| 3.9.0 | 4/30/2025 |
| 3.8.0 | 5/22/2025 |
| 3.7.0 | 5/18/2025 |
| 3.6.1 | 6/3/2025 |
| 3.6.0 | 5/13/2025 |
| 3.5.0 | 5/19/2025 |
| 3.4.0 | 5/2/2025 |
| 3.3.0 | 5/5/2025 |
| 3.2.0 | 6/4/2025 |
| 3.1.0 | 5/29/2025 |
| 3.0.1 | 5/2/2025 |
| 3.0.0 | 6/7/2025 |
| 2.2.0 | 6/8/2025 |
| 2.1.0 | 4/17/2025 |
| 2.0.0 | 5/12/2025 |
| 2.0.0-preview3 | 6/6/2025 |
| 2.0.0-preview2 | 5/11/2025 |
| 2.0.0-preview | 6/10/2025 |
| 1.3.1 | 6/9/2025 |
| 1.3.0 | 5/23/2025 |
| 1.2.0 | 5/19/2025 |
| 1.1.0 | 5/4/2025 |
| 1.0.0 | 6/9/2025 |