@@ -37,7 +37,7 @@ services.AddSessionBasedCaptcha();
3737
3838``` csharp
3939// Don't forget to add this line in your `Configure` method.
40- app .UseSession ();
40+ app .UseSession ();
4141```
4242
4343or you can customize the options
@@ -186,7 +186,6 @@ public async Task<IActionResult> SomeAction(YourModelWithCaptchaCode model)
186186 }
187187```
188188
189-
190189## Stateless Captcha (Recommended for Scalable Applications)
191190
192191** Advantages of Stateless Captcha:**
@@ -227,155 +226,14 @@ public class StatelessHomeModel
227226}
228227```
229228
230- ### 3. Example Controller
231-
232- ``` csharp
233- using Edi .Captcha .SampleApp .Models ;
234- using Microsoft .AspNetCore .Mvc ;
235- using System ;
236- using System .Diagnostics ;
237-
238- namespace Edi .Captcha .SampleApp .Controllers ;
239-
240- public class StatelessController (IStatelessCaptcha captcha ) : Controller
241- {
242- public IActionResult Index ()
243- {
244- return View (new StatelessHomeModel ());
245- }
246-
247- [HttpPost ]
248- public IActionResult Index (StatelessHomeModel model )
249- {
250- if (ModelState .IsValid )
251- {
252- bool isValidCaptcha = captcha .Validate (model .CaptchaCode , model .CaptchaToken );
253- return Content (isValidCaptcha ? " Success - Stateless captcha validated!" : " Invalid captcha code" );
254- }
255-
256- return BadRequest ();
257- }
258-
259- [Route (" get-stateless-captcha" )]
260- public IActionResult GetStatelessCaptcha ()
261- {
262- var result = captcha .GenerateCaptcha (100 , 36 );
263-
264- return Json (new {
265- token = result .Token ,
266- imageBase64 = Convert .ToBase64String (result .ImageBytes )
267- });
268- }
269-
270- [ResponseCache (Duration = 0 , Location = ResponseCacheLocation .None , NoStore = true )]
271- public IActionResult Error ()
272- {
273- return View (new ErrorViewModel { RequestId = Activity .Current ? .Id ?? HttpContext .TraceIdentifier });
274- }
275- }
276- ```
277-
278- ### 4. Example View
279-
280- ``` razor
281- @model StatelessHomeModel
282- @{
283- ViewData["Title"] = "Stateless Captcha Example";
284- }
285-
286- <div class="text-center">
287- <h1 class="display-4">Stateless Captcha Example</h1>
288- <p>This example shows how to use stateless captcha that works in clustered environments.</p>
289- </div>
290-
291- <div class="row">
292- <div class="col-md-6 offset-md-3">
293- <div class="card">
294- <div class="card-header">
295- <h5>Stateless Captcha Form</h5>
296- </div>
297- <div class="card-body">
298- <form asp-action="Index" method="post" id="stateless-form">
299- <div class="form-group mb-3">
300- <label>Captcha Image:</label>
301- <div class="d-flex align-items-center">
302- <img id="captcha-image" src="" alt="Captcha" class="me-2" style="border: 1px solid #ccc;" />
303- <button type="button" class="btn btn-sm btn-outline-secondary" onclick="refreshCaptcha()">
304- 🔄 Refresh
305- </button>
306- </div>
307- <small class="form-text text-muted">Click refresh to get a new captcha</small>
308- </div>
309-
310- <div class="form-group mb-3">
311- <label asp-for="CaptchaCode">Enter Captcha Code:</label>
312- <input asp-for="CaptchaCode" class="form-control" placeholder="Enter the code from image" autocomplete="off" />
313- <span asp-validation-for="CaptchaCode" class="text-danger"></span>
314- </div>
315-
316- <input type="hidden" asp-for="CaptchaToken" id="captcha-token" />
317-
318- <div class="form-group">
319- <button type="submit" class="btn btn-primary">Submit</button>
320- <a asp-controller="Home" asp-action="Index" class="btn btn-secondary">Session-based Example</a>
321- </div>
322- </form>
323-
324- <div class="mt-4">
325- <h6>Advantages of Stateless Captcha:</h6>
326- <ul class="small">
327- <li>✅ Works in clustered/load-balanced environments</li>
328- <li>✅ No server-side session storage required</li>
329- <li>✅ Built-in expiration through encryption</li>
330- <li>✅ Secure token-based validation</li>
331- <li>✅ Better scalability</li>
332- <li>✅ Single API call for both token and image</li>
333- </ul>
334- </div>
335- </div>
336- </div>
337- </div>
338- </div>
229+ ### 3. Example Controller and View
339230
340- <script>
341- async function refreshCaptcha() {
342- try {
343- const response = await fetch('/get-stateless-captcha');
344- const data = await response.json();
345-
346- // Set the token for validation
347- document.getElementById('captcha-token').value = data.token;
348-
349- // Set the image source using base64 data
350- document.getElementById('captcha-image').src = `data:image/png;base64,${data.imageBase64}`;
351-
352- // Clear the input
353- document.getElementById('CaptchaCode').value = '';
354- } catch (error) {
355- console.error('Error refreshing captcha:', error);
356- alert('Failed to load captcha. Please try again.');
357- }
358- }
231+ See: [ src\Edi.Captcha.SampleApp\Controllers\StatelessController.cs] ( src/Edi.Captcha.SampleApp/Controllers/StatelessController.cs ) and [ src\Edi.Captcha.SampleApp\Views\Stateless\Index.cshtml] ( src/Edi.Captcha.SampleApp/Views/Stateless/Index.cshtml ) for a complete example.
359232
360- // Initialize captcha on page load
361- document.addEventListener('DOMContentLoaded', function() {
362- refreshCaptcha();
363- });
364- </script>
365-
366- @section Scripts {
367- @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
368- }
369- ```
370-
371- ## Cluster/Load Balancer Configuration
233+ ### Cluster/Load Balancer Configuration
372234
373235⚠️ ** 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.
374236
375- ### Configure Shared Data Protection Keys
376-
377- Choose one of the following approaches based on your infrastructure:
378-
379237#### Option 1: File System (Network Share)
380238``` csharp
381239public void ConfigureServices (IServiceCollection services )
@@ -436,11 +294,11 @@ public void ConfigureServices(IServiceCollection services)
436294}
437295```
438296
439- ### Single Server Deployment
297+ #### Single Server Deployment
440298
441299For single server deployments, no additional configuration is required. The default Data Protection configuration will work correctly.
442300
443- ### Testing Cluster Configuration
301+ #### Testing Cluster Configuration
444302
445303To verify your cluster configuration is working:
446304
@@ -450,3 +308,70 @@ To verify your cluster configuration is working:
450308
451309If validation fails with properly entered captcha codes, check your Data Protection configuration.
452310
311+ ## Shared Key Stateless Captcha (Recommended for Scalable Applications without DPAPI)
312+
313+ ** When to use Shared Key Stateless Captcha:**
314+ - ✅ Full control over encryption keys
315+ - ✅ Works without ASP.NET Core Data Protection API
316+ - ✅ Simpler cluster configuration
317+ - ✅ Custom key rotation strategies
318+ - ✅ Works across different application frameworks
319+ - ✅ No dependency on external storage for keys
320+
321+ ### 1. Register in DI with Shared Key
322+
323+ ``` csharp
324+ services .AddSharedKeyStatelessCaptcha (options =>
325+ {
326+ options .SharedKey = " your-32-byte-base64-encoded-key" ; // Generate securely
327+ options .FontStyle = FontStyle .Bold ;
328+ options .DrawLines = true ;
329+ options .TokenExpiration = TimeSpan .FromMinutes (5 );
330+ });
331+ ```
332+
333+ ### 2. Generate Secure Shared Key
334+
335+ ** Important** : Use a cryptographically secure random key. Here's how to generate one:
336+
337+ ``` csharp
338+ // Generate a secure 256-bit key (one-time setup)
339+ using (var rng = RandomNumberGenerator .Create ())
340+ {
341+ var keyBytes = new byte [32 ]; // 256 bits
342+ rng .GetBytes (keyBytes );
343+ var base64Key = Convert .ToBase64String (keyBytes );
344+ Console .WriteLine ($" Shared Key: {base64Key }" );
345+ }
346+ ```
347+
348+ ### 3. Configuration Options
349+
350+ #### Configuration File (appsettings.json)
351+ ``` json
352+ {
353+ "CaptchaSettings" : {
354+ "SharedKey" : " your-generated-base64-key-here" ,
355+ "TokenExpirationMinutes" : 5
356+ }
357+ }
358+ ```
359+
360+ ``` csharp
361+ public void ConfigureServices (IServiceCollection services )
362+ {
363+ var captchaKey = Configuration [" CaptchaSettings:SharedKey" ];
364+ var expirationMinutes = Configuration .GetValue <int >(" CaptchaSettings:TokenExpirationMinutes" , 5 );
365+
366+ services .AddSharedKeyStatelessCaptcha (options =>
367+ {
368+ options .SharedKey = captchaKey ;
369+ options .TokenExpiration = TimeSpan .FromMinutes (expirationMinutes );
370+ // Other options...
371+ });
372+ }
373+ ```
374+
375+ ### 4. Example Controller and View
376+
377+ See: [ src\Edi.Captcha.SampleApp\Controllers\SharedKeyStatelessController.cs] ( src/Edi.Captcha.SampleApp/Controllers/SharedKeyStatelessController.cs ) and [ src\Edi.Captcha.SampleApp\Views\SharedKeyStateless\Index.cshtml] ( src/Edi.Captcha.SampleApp/Views/SharedKeyStateless/Index.cshtml ) for a complete example.
0 commit comments