Auth Shield

Auth Shield protects your login endpoints from brute-force attacks and credential stuffing. When enabled, it monitors authentication attempts per IP, automatically locks out offending clients after repeated failures, and emits threat events into the Sentinel pipeline.

How It Differs from Rate Limiting

Rate limiting controls overall request volume. Auth Shield is purpose-built for login endpoints — it tracks failed authentication outcomes (non-2xx responses), not just request counts. A legitimate user who logs in successfully on the first try is never affected.

Configuration

Auth Shield is configured through the AuthShieldConfig section of sentinel.Config. All fields have sensible defaults except LoginRoute, which must match your actual login endpoint path.

FieldTypeDefaultDescription
EnabledboolfalseEnables the Auth Shield middleware.
LoginRoutestring""The exact path of your login endpoint (e.g., /api/login). Must match the route registered in Gin.
MaxFailedAttemptsint5Number of failed login attempts within the lockout window before the IP is locked out.
LockoutDurationtime.Duration15 * time.MinuteHow long an IP remains locked out after exceeding MaxFailedAttempts. Also used as the sliding window for counting failures.
CredentialStuffingDetectionboolfalseWhen enabled, detects a single IP trying many different usernames (more than 10 unique usernames within the window).
BruteForceDetectionboolfalseWhen enabled, detects repeated password guessing against the same username.

How It Works

Auth Shield registers as Gin middleware and intercepts only POST requests to the configured LoginRoute. All other routes and HTTP methods pass through untouched. The flow is:

  1. Pre-check — Before your login handler runs, Auth Shield checks if the client IP is currently locked out. If it is, the request is immediately rejected with a 429 Too Many Requests response.
  2. Passthrough — If the IP is not locked, the request proceeds to your login handler as normal.
  3. Observe response — After your handler responds, Auth Shield inspects the HTTP status code:
    • 2xx — Successful login. The failure counter for that IP (and username, if provided) is reset to zero.
    • 4xx — Failed login. The failure is recorded with a timestamp.
  4. Lockout — If the number of failures from an IP within the LockoutDuration window reaches MaxFailedAttempts, the IP is locked out for the full LockoutDuration.

LoginRoute Must Match Exactly

The LoginRoute must be the exact path string of your login handler (e.g., /api/login). It is compared against c.Request.URL.Path. If it does not match, Auth Shield will not intercept the request and no failures will be tracked.

Example Configuration

A typical production setup with 5 allowed attempts and a 15-minute lockout:

main.gogo
1package main
2
3import (
4 "time"
5
6 sentinel "github.com/MUKE-coder/sentinel"
7 "github.com/gin-gonic/gin"
8)
9
10func main() {
11 r := gin.Default()
12
13 sentinel.Mount(r, nil, sentinel.Config{
14 AuthShield: sentinel.AuthShieldConfig{
15 Enabled: true,
16 LoginRoute: "/api/login",
17 MaxFailedAttempts: 5,
18 LockoutDuration: 15 * time.Minute,
19 CredentialStuffingDetection: true,
20 BruteForceDetection: true,
21 },
22 })
23
24 r.POST("/api/login", func(c *gin.Context) {
25 // Your login logic here
26 username := c.PostForm("username")
27 password := c.PostForm("password")
28
29 // Optionally set the username so Auth Shield can track per-user failures
30 c.Set("sentinel_username", username)
31
32 if username == "admin" && password == "correct-password" {
33 c.JSON(200, gin.H{"token": "jwt-token-here"})
34 } else {
35 c.JSON(401, gin.H{"error": "Invalid credentials"})
36 }
37 })
38
39 r.Run(":8080")
40}

Username Tracking

To enable per-username failure tracking and credential stuffing detection, set c.Set("sentinel_username", username) in your login handler before writing the response. Auth Shield reads this value from the Gin context. If not set, only IP-based tracking is used.

What Gets Tracked

Auth Shield determines success or failure based on the HTTP status code returned by your login handler:

Status CodeInterpretationAction
200-299Successful loginReset all failure counters for the IP and username.
400-499Failed loginIncrement the failure counter. Lock the IP if MaxFailedAttempts is reached.
500+Server errorIgnored. Server errors are not counted as failed login attempts.

Failure timestamps older than LockoutDuration are automatically pruned from the sliding window, so a slow trickle of failures over a long period will not trigger a lockout.

Design Your Login Handler Accordingly

Return a 401 for invalid credentials and a 200 for successful logins. Avoid returning 200 with an error in the body, as Auth Shield will treat it as a success and reset the counter.

Lockout Response

When a locked-out IP attempts to access the login route, Auth Shield returns the following response without invoking your login handler:

429 Too Many Requestsjson
{
"error": "Too many failed login attempts. Please try again later.",
"code": "AUTH_SHIELD_LOCKED"
}

Events

Auth Shield emits threat events into the Sentinel pipeline whenever a lockout is triggered or credential stuffing is detected. These events flow through the same pipeline as WAF and anomaly events, meaning they are:

  • Stored in the configured storage backend
  • Visible in the dashboard Threats page
  • Eligible for alerting (Slack, email, webhook) based on severity
  • Available for AI analysis if an AI provider is configured

Threat Event Types

Threat TypeTriggerSeverity
BruteForceIP locked out after exceeding MaxFailedAttemptsHigh
CredentialStuffingSingle IP tries more than 10 different usernames within the windowHigh

Each event includes the offending IP, the login route path, a confidence score of 90, and evidence detailing the specific detection pattern.

Testing

You can verify Auth Shield is working by sending failed login requests and observing the lockout behavior. The following example assumes the default configuration with 5 max attempts.

Trigger a Lockout

# Send 5 failed login attempts (invalid credentials)
for i in $(seq 1 5); do
echo "Attempt $i:"
curl -s -o /dev/null -w "HTTP %{http_code}" \
-X POST http://localhost:8080/api/login \
-d "username=admin&password=wrong"
echo ""
done
# Attempt 1-5: HTTP 401 (failed login, counter incrementing)
# After 5 failures, the IP is now locked out.

Verify the Lockout

# The 6th attempt should return 429 (locked out)
curl -s -w "\nHTTP %{http_code}\n" \
-X POST http://localhost:8080/api/login \
-d "username=admin&password=wrong"
# Expected output:
# {"error":"Too many failed login attempts. Please try again later.","code":"AUTH_SHIELD_LOCKED"}
# HTTP 429

Verify Successful Login Still Works (from a different IP)

# Even correct credentials from the locked IP are rejected
curl -s -w "\nHTTP %{http_code}\n" \
-X POST http://localhost:8080/api/login \
-d "username=admin&password=correct-password"
# HTTP 429 (still locked — must wait for LockoutDuration to expire)

Testing Locally

When testing locally, all requests come from 127.0.0.1 or ::1, so they share a single IP counter. To test with different IPs, use the X-Forwarded-For header if your setup supports it, or test from different machines.

Dashboard

Auth Shield events appear in the Sentinel dashboard on the Threats page. Each event includes:

  • Threat type displayed as BruteForce or CredentialStuffing
  • The offending IP address
  • Timestamp of when the lockout was triggered
  • Severity level (High)
  • Evidence showing the detection pattern and location (auth_shield)

You can filter the Threats page by type to isolate brute-force and credential stuffing events. If alerting is configured with a minimum severity of High or lower, these events will also trigger Slack, email, or webhook notifications.

Combining with Rate Limiting

Auth Shield and rate limiting are complementary. A recommended pattern is to use both:

config.gogo
1sentinel.Config{
2 // Auth Shield: locks out IPs after failed login attempts
3 AuthShield: sentinel.AuthShieldConfig{
4 Enabled: true,
5 LoginRoute: "/api/login",
6 MaxFailedAttempts: 5,
7 LockoutDuration: 15 * time.Minute,
8 CredentialStuffingDetection: true,
9 BruteForceDetection: true,
10 },
11
12 // Rate Limiting: caps overall request volume to the login route
13 RateLimit: sentinel.RateLimitConfig{
14 Enabled: true,
15 ByRoute: map[string]sentinel.Limit{
16 "/api/login": {Requests: 10, Window: 15 * time.Minute},
17 },
18 },
19}

In this setup, rate limiting prevents any IP from making more than 10 requests to the login route in 15 minutes (regardless of success or failure), while Auth Shield specifically tracks authentication failures and locks out after 5 failed attempts.

Next Steps


Built with by JB