Security best practices and common mistakes to avoid in CWaptcha deployments.
Set RequireHttps: true in production. CWaptcha's HMAC provides field integrity, but the nonce and fieldSalt must be protected in transit by TLS. SubtleCrypto also requires a secure context (HTTPS or localhost).
Default is 300 seconds (5 minutes). Longer TTLs widen the window for token theft and replay. For high-security forms (password reset, payment), consider 60–120 seconds.
Call .UseDistributedNonceStore() after AddCWaptcha() whenever your app runs behind a load balancer without sticky sessions. Without it, nonces on one node are invisible to others.
Use dotnet user-secrets in development, environment variables in production. A committed SecretKey allows anyone who can read your repo to forge valid CAPTCHA tokens.
Only include the POST path in ProtectedPaths (e.g. "/Contact", not "/Contact/Post"). Middleware validates POST only. Protecting the GET blocks the page from loading.
Combine with rate limiting (ASP.NET Core Rate Limiting middleware), input validation, and server-side authentication. CWaptcha is bot protection, not a silver bullet.
The middleware and the attribute both redeem the nonce. If both run on the same request, the middleware consumes the nonce and the attribute sees it as already used — the request always fails. Choose one mechanism per route.
Each page load issues a fresh nonce. Navigating back in the browser, then submitting, uses an already-redeemed token. This is by design — it prevents replay attacks.
In-memory state is per-process. A request that lands on a different node than the one that issued the nonce will fail to find it. Use UseDistributedNonceStore() instead.
The script is embedded in the NuGet package and served from /cwaptcha/cwaptcha.js by the middleware. Hosting it externally breaks the HMAC field salt mechanism and defeats the self-hosted security model.
On HTTP, the fieldSalt is transmitted in plaintext and could be intercepted to forge a valid token. Always enforce HTTPS in production — either via RequireHttps: true or at the reverse-proxy / IIS level.