Security Plugin (sentinel)

The `sentinel()` plugin provides comprehensive security and abuse protection for your authentication system. It detects and prevents various attack vectors including credential stuffing, impossible travel, free trial abuse, and more.

Installation

import { betterAuth } from "better-auth";
import { sentinel } from "@better-auth/infra";

export const auth = betterAuth({
  plugins: [
    sentinel({
      security: {
        // Configure security features here
      },
    }),
  ],
});

Configuration Options

SentinelOptions

OptionTypeDescription
apiUrlstringBetter Auth Infrastructure API URL
kvUrlstringKV store URL for rate limiting data
apiKeystringYour API key for authentication
securitySecurityOptionsSecurity feature configuration

SecurityOptions

interface SecurityOptions {
  unknownDeviceNotification?: boolean;
  credentialStuffing?: CredentialStuffingConfig;
  impossibleTravel?: ImpossibleTravelConfig;
  geoBlocking?: GeoBlockingConfig;
  botBlocking?: boolean | { action: SecurityAction };
  suspiciousIpBlocking?: boolean | { action: SecurityAction };
  velocity?: VelocityConfig;
  freeTrialAbuse?: FreeTrialAbuseConfig;
  compromisedPassword?: CompromisedPasswordConfig;
  emailValidation?: EmailValidationConfig;
  staleUsers?: StaleUsersConfig;
  challengeDifficulty?: number;
}

type SecurityAction = "log" | "challenge" | "block";

Security Features

Credential Stuffing Protection

Detects and blocks credential stuffing attacks by tracking failed login attempts per visitor.

sentinel({
  apiKey: process.env.BETTER_AUTH_API_KEY,
  security: {
    credentialStuffing: {
      enabled: true,
      thresholds: {
        challenge: 3,  // Issue PoW challenge after 3 failures
        block: 5,      // Block after 5 failures
      },
      windowSeconds: 3600,     // 1 hour window
      cooldownSeconds: 900,    // 15 minute cooldown after block
    },
  },
}),

How it works:

  1. Tracks failed login attempts per visitor ID
  2. After reaching the challenge threshold, issues a Proof-of-Work challenge
  3. After reaching the block threshold, blocks the visitor entirely
  4. Automatically clears failed attempts on successful login

Impossible Travel Detection

Detects logins from geographically distant locations in impossibly short timeframes.

sentinel({
  apiKey: process.env.BETTER_AUTH_API_KEY,
  security: {
    impossibleTravel: {
      enabled: true,
      maxSpeedKmh: 1000,    // Max realistic travel speed
      action: "challenge",  // "log", "challenge", or "block"
    },
  },
}),

Example: If a user logs in from New York and then 30 minutes later from Tokyo, this would be flagged as impossible travel (would require traveling faster than 1000 km/h).

Free Trial Abuse Prevention

Prevents users from creating multiple accounts to abuse free trials using device fingerprinting.

sentinel({
  apiKey: process.env.BETTER_AUTH_API_KEY,
  security: {
    freeTrialAbuse: {
      enabled: true,
      thresholds: {
        challenge: 2,
        block: 3,
      },
      maxAccountsPerVisitor: 3,
      action: "block",
    },
  },
}),

How it works:

  1. Tracks account creations per visitor fingerprint
  2. When threshold is exceeded, blocks new account creation
  3. Useful for preventing free tier abuse

Compromised Password Detection

Checks passwords against the HaveIBeenPwned database to detect compromised credentials.

sentinel({
  apiKey: process.env.BETTER_AUTH_API_KEY,
  security: {
    compromisedPassword: {
      enabled: true,
      action: "block",      // "log", "challenge", or "block"
      minBreachCount: 1,    // Minimum breaches to trigger
    },
  },
}),

Privacy: Uses k-anonymity - only the first 5 characters of the password hash are sent to the API, never the full password.

Stale Account Monitoring

Detects when dormant accounts suddenly become active, which could indicate account takeover.

sentinel({
  apiKey: process.env.BETTER_AUTH_API_KEY,
  security: {
    staleUsers: {
      enabled: true,
      staleDays: 90,           // Account considered stale after 90 days
      action: "log",           // "log", "challenge", or "block"
      notifyUser: true,        // Send email to user
      notifyAdmin: true,       // Send email to admin
      adminEmail: "admin@yourapp.com",
    },
  },
}),

Notifications include:

  • Login time and location
  • Days since last activity
  • Device information

Geo-Blocking

Block or challenge users from specific countries.

sentinel({
  apiKey: process.env.BETTER_AUTH_API_KEY,
  security: {
    geoBlocking: {
      allowList: ["US", "CA", "GB"],  // Only allow these countries
      // OR
      denyList: ["XX", "YY"],          // Block these countries
      action: "block",                  // "challenge" or "block"
    },
  },
}),

Use ISO 3166-1 alpha-2 country codes.

Bot Blocking

Detect and block automated bot traffic.

sentinel({
  apiKey: process.env.BETTER_AUTH_API_KEY,
  security: {
    botBlocking: true,
    // OR with custom action
    botBlocking: {
      action: "challenge",  // "log", "challenge", or "block"
    },
  },
}),

Suspicious IP Detection

Block requests from known malicious IP addresses.

sentinel({
  apiKey: process.env.BETTER_AUTH_API_KEY,
  security: {
    suspiciousIpBlocking: true,
    // OR with custom action
    suspiciousIpBlocking: {
      action: "block",
    },
  },
}),

Velocity / Rate Limiting

Limit the rate of various operations.

sentinel({
  apiKey: process.env.BETTER_AUTH_API_KEY,
  security: {
    velocity: {
      enabled: true,
      thresholds: {
        challenge: 10,
        block: 20,
      },
      maxSignupsPerVisitor: 5,
      maxPasswordResetsPerIp: 10,
      maxSignInsPerIp: 50,
      windowSeconds: 3600,
      action: "challenge",
    },
  },
}),

Email Validation

Block disposable email addresses and validate email domains.

sentinel({
  apiKey: process.env.BETTER_AUTH_API_KEY,
  security: {
    emailValidation: {
      enabled: true,
      strictness: "medium",  // "low", "medium", or "high"
      action: "block",
    },
  },
}),

Strictness levels:

  • low - Block only known disposable domains
  • medium - Also check for valid MX records
  • high - Additional heuristic checks

Proof-of-Work Challenges

When a security check results in a "challenge" action, Sentinel issues a Proof-of-Work (PoW) challenge that must be solved by the client.

How PoW Works

  1. Server issues a cryptographic challenge
  2. Client must find a solution that satisfies difficulty requirements
  3. Solution is computationally expensive but verification is fast
  4. Prevents automated attacks while allowing legitimate users through

Challenge Difficulty

sentinel({
  apiKey: process.env.BETTER_AUTH_API_KEY,
  security: {
    challengeDifficulty: 18,  // Default difficulty level
  },
}),

Higher difficulty = more computation required = slower for attackers.

Client Integration

sentinelClient()

The client plugin handles device fingerprinting and automatic PoW challenge solving.

import { createAuthClient } from "better-auth/client";
import { sentinelClient } from "@better-auth/infra/client";

export const authClient = createAuthClient({
  plugins: [
    sentinelClient({
      autoSolveChallenge: true,
    }),
  ],
});

Configuration

OptionTypeDefaultDescription
autoSolveChallengebooleantrueAutomatically solve PoW challenges

Browser Fingerprinting

The client automatically includes a visitor ID in requests via the X-Visitor-ID header. This fingerprint is used for:

  • Credential stuffing detection
  • Free trial abuse prevention
  • Device tracking

PoW Solution Header

When auto-solving is enabled, solved challenges are sent via the X-PoW-Solution header.

Security Events

Sentinel tracks the following security event types:

Event TypeDescription
security_blockedRequest was blocked
security_allowedRequest was allowed after challenge
security_credential_stuffingCredential stuffing detected
security_impossible_travelImpossible travel detected
security_geo_blockedGeo-blocking triggered
security_bot_blockedBot detected and blocked
security_suspicious_ipSuspicious IP detected
security_velocity_exceededRate limit exceeded
security_free_trial_abuseFree trial abuse detected
security_compromised_passwordCompromised password detected
security_stale_accountStale account reactivation

These events are visible in the Security dashboard and included in audit logs.

Complete Example

import { betterAuth } from "better-auth";
import { sentinel } from "@better-auth/infra";

export const auth = betterAuth({
  plugins: [
    sentinel({
      apiKey: process.env.BETTER_AUTH_API_KEY,
      security: {
        // Core protections
        credentialStuffing: {
          enabled: true,
          thresholds: { challenge: 3, block: 5 },
        },
        compromisedPassword: {
          enabled: true,
          action: "block",
        },
        emailValidation: {
          enabled: true,
          strictness: "medium",
          action: "block",
        },
        
        // Location-based
        impossibleTravel: {
          enabled: true,
          action: "challenge",
        },
        geoBlocking: {
          denyList: ["XX"],
          action: "block",
        },
        
        // Abuse prevention
        freeTrialAbuse: {
          enabled: true,
          maxAccountsPerVisitor: 3,
          action: "block",
        },
        velocity: {
          enabled: true,
          maxSignupsPerVisitor: 5,
          action: "challenge",
        },
        
        // Bot protection
        botBlocking: { action: "challenge" },
        suspiciousIpBlocking: { action: "block" },
        
        // Account monitoring
        staleUsers: {
          enabled: true,
          staleDays: 90,
          notifyUser: true,
          notifyAdmin: true,
          adminEmail: "security@yourapp.com",
        },
      },
    }),
  ],
});

Best Practices

  1. Start with logging - Set actions to "log" initially to understand your traffic patterns before blocking.

  2. Tune thresholds - Every application is different. Monitor false positives and adjust thresholds accordingly.

  3. Use challenges before blocks - Challenges allow legitimate users through while stopping automated attacks.

  4. Enable client-side auto-solve - Make sure sentinelClient({ autoSolveChallenge: true }) is configured for smooth user experience.

  5. Monitor security events - Regularly review the Security dashboard to identify attack patterns.

  6. Set up admin notifications - Enable notifyAdmin for critical events like stale account reactivations.

Troubleshooting

Missing API Key Warning

[Sentinel] Missing BETTER_AUTH_API_KEY. Security checks may fall back to allow mode.

Make sure your API key environment variable is set correctly.

Challenges Not Working

If PoW challenges aren't being solved:

  1. Verify sentinelClient() is installed on the client
  2. Check that autoSolveChallenge is true
  3. Ensure the client can reach the server

High False Positive Rate

If legitimate users are being blocked:

  1. Increase thresholds
  2. Change action from "block" to "challenge"
  3. Review security events to identify patterns