Skip to main content

Overview

The captcha 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

import "github.com/go-mizu/mizu/middlewares/captcha"

Quick Start

app := mizu.New()

// Google reCAPTCHA v2
app.Use(captcha.ReCaptchaV2("your-secret-key"))

Configuration

Options

OptionTypeDefaultDescription
ProviderProviderProviderRecaptchaV2CAPTCHA provider
Secretstring-Secret key from provider
TokenLookupstring"form:g-recaptcha-response"Where to find token
MinScorefloat640.5Minimum score for v3 providers
Verifierfunc(token string, c *mizu.Ctx) (bool, error)-Custom verifier
ErrorHandlerfunc(*mizu.Ctx, error) error-Custom error handler
SkipPaths[]string-Paths to skip verification
Timeouttime.Duration10sVerification timeout

Providers

const (
    ProviderRecaptchaV2 // Google reCAPTCHA v2 (checkbox)
    ProviderRecaptchaV3 // Google reCAPTCHA v3 (invisible)
    ProviderHCaptcha    // hCaptcha
    ProviderTurnstile   // Cloudflare Turnstile
    ProviderCustom      // Custom verification
)

Examples

Google reCAPTCHA v2

app.Use(captcha.ReCaptchaV2(os.Getenv("RECAPTCHA_SECRET")))
Frontend:
<script src="https://www.google.com/recaptcha/api.js" async defer></script>
<form method="POST" action="/submit">
    <div class="g-recaptcha" data-sitekey="your-site-key"></div>
    <button type="submit">Submit</button>
</form>

Google reCAPTCHA v3

// Require minimum score of 0.7
app.Use(captcha.ReCaptchaV3(os.Getenv("RECAPTCHA_SECRET"), 0.7))
Frontend:
<script src="https://www.google.com/recaptcha/api.js?render=your-site-key"></script>
<script>
grecaptcha.ready(function() {
    grecaptcha.execute('your-site-key', {action: 'submit'})
    .then(function(token) {
        document.getElementById('captcha-token').value = token;
    });
});
</script>
<form method="POST" action="/submit">
    <input type="hidden" id="captcha-token" name="g-recaptcha-response">
    <button type="submit">Submit</button>
</form>

hCaptcha

app.Use(captcha.HCaptcha(os.Getenv("HCAPTCHA_SECRET")))
Frontend:
<script src="https://js.hcaptcha.com/1/api.js" async defer></script>
<form method="POST" action="/submit">
    <div class="h-captcha" data-sitekey="your-site-key"></div>
    <button type="submit">Submit</button>
</form>

Cloudflare Turnstile

app.Use(captcha.Turnstile(os.Getenv("TURNSTILE_SECRET")))
Frontend:
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
<form method="POST" action="/submit">
    <div class="cf-turnstile" data-sitekey="your-site-key"></div>
    <button type="submit">Submit</button>
</form>

Skip Certain Paths

app.Use(captcha.WithOptions(captcha.Options{
    Provider:  captcha.ProviderRecaptchaV2,
    Secret:    os.Getenv("RECAPTCHA_SECRET"),
    SkipPaths: []string{
        "/api/webhook",    // External webhooks
        "/api/health",     // Health checks
    },
}))

Custom Token Location

// Token from header instead of form
app.Use(captcha.WithOptions(captcha.Options{
    Provider:    captcha.ProviderRecaptchaV3,
    Secret:      secret,
    TokenLookup: "header:X-Captcha-Token",
}))

Custom Error Handler

app.Use(captcha.WithOptions(captcha.Options{
    Provider: captcha.ProviderRecaptchaV2,
    Secret:   secret,
    ErrorHandler: func(c *mizu.Ctx, err error) error {
        if err == captcha.ErrMissingToken {
            return c.JSON(400, map[string]string{
                "error": "Please complete the CAPTCHA",
            })
        }
        return c.JSON(403, map[string]string{
            "error": "CAPTCHA verification failed. Please try again.",
        })
    },
}))

Custom Verifier

app.Use(captcha.Custom(
    func(token string, c *mizu.Ctx) (bool, error) {
        // Custom verification logic
        return myVerifier.Verify(token, c.ClientIP())
    },
    "form:my-captcha-token",
))

Route-Specific Protection

// Only protect specific routes
registerCaptcha := captcha.ReCaptchaV2(secret)
loginCaptcha := captcha.ReCaptchaV3(secret, 0.5)

app.Post("/register", registerHandler, registerCaptcha)
app.Post("/login", loginHandler, loginCaptcha)

API Reference

Functions

// New creates middleware with default provider
func New(secret string) mizu.Middleware

// WithOptions creates middleware with custom options
func WithOptions(opts Options) mizu.Middleware

// Provider-specific constructors
func ReCaptchaV2(secret string) mizu.Middleware
func ReCaptchaV3(secret string, minScore float64) mizu.Middleware
func HCaptcha(secret string) mizu.Middleware
func Turnstile(secret string) mizu.Middleware

// Custom creates middleware with custom verifier
func Custom(verifier func(string, *mizu.Ctx) (bool, error), tokenLookup string) mizu.Middleware

Error Types

var (
    ErrMissingToken // Token not found in request
    ErrInvalidToken // Token verification failed
    ErrVerifyFailed // Verification request failed
)

Provider Comparison

ProviderTypePrivacyFree Tier
reCAPTCHA v2CheckboxGoogleYes
reCAPTCHA v3InvisibleGoogleYes
hCaptchaCheckboxPrivacy-focusedYes
TurnstileInvisibleCloudflareYes

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
The TokenLookup option uses the format source:key where source is one of form, header, or query.

Verification Flow

  1. Request Filtering: Skips verification for safe methods (GET, HEAD, OPTIONS) and configured skip paths
  2. Token Extraction: Retrieves token based on TokenLookup configuration
  3. Verification:
    • For custom verifiers: Calls the provided Verifier function
    • For provider verifiers: Makes HTTP POST to provider’s API with secret, token, and client IP
  4. Score Validation: For v3 providers (reCAPTCHA v3, Turnstile), validates score against MinScore threshold
  5. Error Handling: Uses custom ErrorHandler if provided, otherwise returns default 400 response

Client IP Detection

The middleware detects client IP in the following order:
  1. X-Forwarded-For header (first IP in comma-separated list)
  2. X-Real-IP header
  3. RemoteAddr from request (strips port if present)
The detected IP is sent to the provider API for enhanced verification.

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
The middleware sends:
  • secret: Your secret key
  • response: The CAPTCHA token
  • remoteip: Client IP address (optional)

Default Values

SettingDefault Value
ProviderProviderRecaptchaV2
TokenLookup"form:g-recaptcha-response"
MinScore0.5
Timeout10s

Error Types

The middleware defines three specific error types:
  • ErrMissingToken: Token not found in the specified lookup location
  • ErrInvalidToken: Token verification failed (invalid or low score)
  • ErrVerifyFailed: HTTP request to provider API failed

Testing

Test Coverage

Test CaseDescriptionExpected Behavior
TestWithOptions_MissingTokenPOST request without CAPTCHA tokenReturns 400 Bad Request
TestWithOptions_CustomVerifier (valid)Custom verifier with valid tokenRequest proceeds (200 OK)
TestWithOptions_CustomVerifier (invalid)Custom verifier with invalid tokenReturns 400 Bad Request
TestWithOptions_SkipSafeMethodsGET request without tokenRequest proceeds (200 OK), CAPTCHA skipped
TestWithOptions_SkipPathsPOST to path in SkipPathsRequest proceeds (200 OK), CAPTCHA skipped
TestWithOptions_HeaderTokenToken provided via HTTP headerToken extracted from header, verification succeeds
TestWithOptions_QueryTokenToken provided via query parameterToken extracted from query, verification succeeds
TestWithOptions_CustomErrorHandlerInvalid token with custom error handlerCustom handler returns 403 Forbidden with JSON
TestCustomCustom verifier functionCustom verification logic applied successfully
TestNewCreating middleware with New()Middleware instance created with defaults
TestErrorsError message validationAll error types return correct messages
TestExtractToken_InvalidLookupTokenLookup without colon separatorReturns 400 Bad Request (token not found)
TestVerifyToken_WithMockServer (valid)Valid token verified by mock serverReturns 200 OK
TestVerifyToken_WithMockServer (invalid)Invalid token verified by mock serverReturns 400 Bad Request
TestVerifyToken_RecaptchaV3Score (high)reCAPTCHA v3 with score above thresholdReturns 200 OK
TestVerifyToken_RecaptchaV3Score (low)reCAPTCHA v3 with score below thresholdReturns 400 Bad Request
TestVerifyToken_ServerErrorProvider API returns 500 errorReturns 400 Bad Request
TestVerifyToken_InvalidJSONProvider returns malformed JSONReturns 400 Bad Request
TestVerifyToken_UnknownProviderUnknown provider typeReturns 400 Bad Request
TestGetClientIP_XForwardedForX-Forwarded-For header presentExtracts first IP from header, sends to provider
TestGetClientIP_XRealIPX-Real-IP header presentExtracts IP from header, sends to provider
TestReCaptchaV2ReCaptchaV2 constructorCreates middleware with reCAPTCHA v2 config
TestReCaptchaV3ReCaptchaV3 constructorCreates middleware with reCAPTCHA v3 config
TestHCaptchaHCaptcha constructorCreates middleware with hCaptcha config
TestTurnstileTurnstile constructorCreates middleware with Turnstile config
TestWithOptions_VerifierErrorCustom verifier returns errorReturns 400 Bad Request
TestWithOptions_HeadMethodHEAD request without tokenRequest proceeds (200 OK), CAPTCHA skipped
TestWithOptions_OptionsMethodOPTIONS request without tokenRequest proceeds (200 OK), CAPTCHA skipped

Security Considerations

  1. Secret Key - Never expose secret key in frontend code
  2. Score Tuning - Adjust v3 scores based on traffic patterns
  3. Timeout - Set reasonable timeout for verification
  4. Fallback - Have manual review process for edge cases