Skip to main content
Version: 2026.04.0

Bot Protection

Primus Auth Broker includes a built-in tiered rate limiter that defends local login endpoints against credential-stuffing and brute-force attacks. Optionally, it can require a CAPTCHA token after a configurable number of failed attempts.


Enable Bot Protection

Add a BotProtection section to your PrimusAuth configuration:

appsettings.json
{
"PrimusAuth": {
"BotProtection": {
"Enabled": true,
"MaxAttemptsPerMinute": 5,
"MaxAttemptsPerHour": 30,
"ChallengeAfterFailures": 3
}
}
}
FieldTypeDefaultAllowed valuesDescription
Enabledboolfalsetrue | falseActivates bot protection on the local login endpoint.
MaxAttemptsPerMinuteint5Positive integerMax login attempts from one IP per 60-second window before HTTP 429.
MaxAttemptsPerHourint30Positive integerMax login attempts from one IP per 60-minute window before HTTP 429.
ChallengeAfterFailuresint3Positive integer, or 0 to disable CAPTCHANumber of consecutive failed attempts from the same IP before a CAPTCHA token is required. Set to 0 to disable the CAPTCHA gate entirely.

Environment Variable Override

ASP.NET Core maps __ to : so all config keys work as environment variables (e.g. PrimusAuth__BotProtection__Enabled=true, PrimusAuth__BotProtection__MaxAttemptsPerMinute=5).


How It Works

The rate limiter is keyed by the client's IP address and operates across two independent windows:

  1. Per-minute window — blocks the request immediately if the IP has exceeded MaxAttemptsPerMinute in the current 60-second period.
  2. Per-hour window — blocks the request if the IP has exceeded MaxAttemptsPerHour in the current 60-minute period.

When either limit is exceeded the endpoint returns HTTP 429 Too Many Requests.

After ChallengeAfterFailures consecutive authentication failures from the same IP, subsequent requests must include a valid captchaToken in the login request body. The CAPTCHA requirement is cleared automatically when a login succeeds from that IP.


CAPTCHA Integration

The SDK ships built-in validators for the four most common CAPTCHA providers. Register one before AddPrimusAuthBroker — no custom implementation required:

// Google reCAPTCHA v2 ("I'm not a robot" checkbox)
builder.Services.AddPrimusRecaptchaV2(secretKey: "your-recaptcha-v2-secret");

// Google reCAPTCHA v3 (invisible, score-based — default min score: 0.5)
builder.Services.AddPrimusRecaptchaV3(secretKey: "your-recaptcha-v3-secret", minScore: 0.5);

// hCaptcha
builder.Services.AddPrimusHCaptcha(secretKey: "your-hcaptcha-secret");

// Cloudflare Turnstile
builder.Services.AddPrimusTurnstile(secretKey: "your-turnstile-secret");

builder.Services.AddPrimusAuthBroker(builder.Configuration, builder.Environment.IsDevelopment());

Store the secret key in your CI/CD secret store or dotnet user-secrets — never in source-controlled config files.

Login Request with CAPTCHA Token

When CAPTCHA is required, pass the token your frontend received from the CAPTCHA widget in the request body alongside the credentials:

{
"email": "alice@acme.com",
"password": "...",
"captchaToken": "<recaptcha-or-turnstile-response-token>"
}

The captchaToken field is ignored when the CAPTCHA threshold has not yet been reached.


Frontend Integration

After ChallengeAfterFailures consecutive failures, the login endpoint returns HTTP 403:

{
"error": "CAPTCHA required.",
"captchaRequired": true
}

Your frontend should detect captchaRequired: true and render the CAPTCHA widget before re-submitting.


Behaviour Notes

  • Rate limiting runs in-memory using ConcurrentDictionary. It is per-process — if you run multiple app instances, each instance maintains its own counters. For distributed rate limiting, replace the default TieredInMemoryRateLimiter with a Redis-backed implementation.
  • Failure counters are reset automatically after a successful login from the same IP.
  • Only the local login endpoint (POST /api/auth/login) is subject to bot protection. OIDC and SAML flows are handled by the external identity provider.

Next Steps