Overview
Thecaptcha middleware verifies CAPTCHA tokens to protect forms and APIs from bots. It supports multiple providers including Google reCAPTCHA (v2/v3), hCaptcha, and Cloudflare Turnstile.
Use it when you need:
- Bot protection for forms
- Spam prevention
- Account creation protection
- API abuse prevention
Installation
Quick Start
Configuration
Options
| Option | Type | Default | Description |
|---|---|---|---|
Provider | Provider | ProviderRecaptchaV2 | CAPTCHA provider |
Secret | string | - | Secret key from provider |
TokenLookup | string | "form:g-recaptcha-response" | Where to find token |
MinScore | float64 | 0.5 | Minimum score for v3 providers |
Verifier | func(token string, c *mizu.Ctx) (bool, error) | - | Custom verifier |
ErrorHandler | func(*mizu.Ctx, error) error | - | Custom error handler |
SkipPaths | []string | - | Paths to skip verification |
Timeout | time.Duration | 10s | Verification timeout |
Providers
Examples
Google reCAPTCHA v2
Google reCAPTCHA v3
hCaptcha
Cloudflare Turnstile
Skip Certain Paths
Custom Token Location
Custom Error Handler
Custom Verifier
Route-Specific Protection
API Reference
Functions
Error Types
Provider Comparison
| Provider | Type | Privacy | Free Tier |
|---|---|---|---|
| reCAPTCHA v2 | Checkbox | Yes | |
| reCAPTCHA v3 | Invisible | Yes | |
| hCaptcha | Checkbox | Privacy-focused | Yes |
| Turnstile | Invisible | Cloudflare | Yes |
Best Practices
- Use v3/invisible CAPTCHAs for better UX
- Set appropriate score thresholds for v3
- Monitor verification failures for tuning
- Have fallback for CAPTCHA failures
- Test with automation tools
Technical Details
Token Extraction
The middleware supports extracting tokens from three locations:- Form:
form:field-name- Extracts from POST form data - Header:
header:Header-Name- Extracts from HTTP headers - Query:
query:param-name- Extracts from URL query parameters
TokenLookup option uses the format source:key where source is one of form, header, or query.
Verification Flow
- Request Filtering: Skips verification for safe methods (GET, HEAD, OPTIONS) and configured skip paths
- Token Extraction: Retrieves token based on
TokenLookupconfiguration - Verification:
- For custom verifiers: Calls the provided
Verifierfunction - For provider verifiers: Makes HTTP POST to providerβs API with secret, token, and client IP
- For custom verifiers: Calls the provided
- Score Validation: For v3 providers (reCAPTCHA v3, Turnstile), validates score against
MinScorethreshold - Error Handling: Uses custom
ErrorHandlerif provided, otherwise returns default 400 response
Client IP Detection
The middleware detects client IP in the following order:X-Forwarded-Forheader (first IP in comma-separated list)X-Real-IPheaderRemoteAddrfrom request (strips port if present)
Provider API Integration
Each provider has a specific verification endpoint:- reCAPTCHA v2/v3:
https://www.google.com/recaptcha/api/siteverify - hCaptcha:
https://hcaptcha.com/siteverify - Turnstile:
https://challenges.cloudflare.com/turnstile/v0/siteverify
secret: Your secret keyresponse: The CAPTCHA tokenremoteip: Client IP address (optional)
Default Values
| Setting | Default Value |
|---|---|
| Provider | ProviderRecaptchaV2 |
| TokenLookup | "form:g-recaptcha-response" |
| MinScore | 0.5 |
| Timeout | 10s |
Error Types
The middleware defines three specific error types:ErrMissingToken: Token not found in the specified lookup locationErrInvalidToken: Token verification failed (invalid or low score)ErrVerifyFailed: HTTP request to provider API failed
Testing
Test Coverage
| Test Case | Description | Expected Behavior |
|---|---|---|
TestWithOptions_MissingToken | POST request without CAPTCHA token | Returns 400 Bad Request |
TestWithOptions_CustomVerifier (valid) | Custom verifier with valid token | Request proceeds (200 OK) |
TestWithOptions_CustomVerifier (invalid) | Custom verifier with invalid token | Returns 400 Bad Request |
TestWithOptions_SkipSafeMethods | GET request without token | Request proceeds (200 OK), CAPTCHA skipped |
TestWithOptions_SkipPaths | POST to path in SkipPaths | Request proceeds (200 OK), CAPTCHA skipped |
TestWithOptions_HeaderToken | Token provided via HTTP header | Token extracted from header, verification succeeds |
TestWithOptions_QueryToken | Token provided via query parameter | Token extracted from query, verification succeeds |
TestWithOptions_CustomErrorHandler | Invalid token with custom error handler | Custom handler returns 403 Forbidden with JSON |
TestCustom | Custom verifier function | Custom verification logic applied successfully |
TestNew | Creating middleware with New() | Middleware instance created with defaults |
TestErrors | Error message validation | All error types return correct messages |
TestExtractToken_InvalidLookup | TokenLookup without colon separator | Returns 400 Bad Request (token not found) |
TestVerifyToken_WithMockServer (valid) | Valid token verified by mock server | Returns 200 OK |
TestVerifyToken_WithMockServer (invalid) | Invalid token verified by mock server | Returns 400 Bad Request |
TestVerifyToken_RecaptchaV3Score (high) | reCAPTCHA v3 with score above threshold | Returns 200 OK |
TestVerifyToken_RecaptchaV3Score (low) | reCAPTCHA v3 with score below threshold | Returns 400 Bad Request |
TestVerifyToken_ServerError | Provider API returns 500 error | Returns 400 Bad Request |
TestVerifyToken_InvalidJSON | Provider returns malformed JSON | Returns 400 Bad Request |
TestVerifyToken_UnknownProvider | Unknown provider type | Returns 400 Bad Request |
TestGetClientIP_XForwardedFor | X-Forwarded-For header present | Extracts first IP from header, sends to provider |
TestGetClientIP_XRealIP | X-Real-IP header present | Extracts IP from header, sends to provider |
TestReCaptchaV2 | ReCaptchaV2 constructor | Creates middleware with reCAPTCHA v2 config |
TestReCaptchaV3 | ReCaptchaV3 constructor | Creates middleware with reCAPTCHA v3 config |
TestHCaptcha | HCaptcha constructor | Creates middleware with hCaptcha config |
TestTurnstile | Turnstile constructor | Creates middleware with Turnstile config |
TestWithOptions_VerifierError | Custom verifier returns error | Returns 400 Bad Request |
TestWithOptions_HeadMethod | HEAD request without token | Request proceeds (200 OK), CAPTCHA skipped |
TestWithOptions_OptionsMethod | OPTIONS request without token | Request proceeds (200 OK), CAPTCHA skipped |
Security Considerations
- Secret Key - Never expose secret key in frontend code
- Score Tuning - Adjust v3 scores based on traffic patterns
- Timeout - Set reasonable timeout for verification
- Fallback - Have manual review process for edge cases