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
| Option | Type | Description |
|---|---|---|
apiUrl | string | Better Auth Infrastructure API URL |
kvUrl | string | KV store URL for rate limiting data |
apiKey | string | Your API key for authentication |
security | SecurityOptions | Security 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:
- Tracks failed login attempts per visitor ID
- After reaching the challenge threshold, issues a Proof-of-Work challenge
- After reaching the block threshold, blocks the visitor entirely
- 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:
- Tracks account creations per visitor fingerprint
- When threshold is exceeded, blocks new account creation
- 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 domainsmedium- Also check for valid MX recordshigh- 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
- Server issues a cryptographic challenge
- Client must find a solution that satisfies difficulty requirements
- Solution is computationally expensive but verification is fast
- 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
| Option | Type | Default | Description |
|---|---|---|---|
autoSolveChallenge | boolean | true | Automatically 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 Type | Description |
|---|---|
security_blocked | Request was blocked |
security_allowed | Request was allowed after challenge |
security_credential_stuffing | Credential stuffing detected |
security_impossible_travel | Impossible travel detected |
security_geo_blocked | Geo-blocking triggered |
security_bot_blocked | Bot detected and blocked |
security_suspicious_ip | Suspicious IP detected |
security_velocity_exceeded | Rate limit exceeded |
security_free_trial_abuse | Free trial abuse detected |
security_compromised_password | Compromised password detected |
security_stale_account | Stale 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
-
Start with logging - Set actions to "log" initially to understand your traffic patterns before blocking.
-
Tune thresholds - Every application is different. Monitor false positives and adjust thresholds accordingly.
-
Use challenges before blocks - Challenges allow legitimate users through while stopping automated attacks.
-
Enable client-side auto-solve - Make sure
sentinelClient({ autoSolveChallenge: true })is configured for smooth user experience. -
Monitor security events - Regularly review the Security dashboard to identify attack patterns.
-
Set up admin notifications - Enable
notifyAdminfor 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:
- Verify
sentinelClient()is installed on the client - Check that
autoSolveChallengeistrue - Ensure the client can reach the server
High False Positive Rate
If legitimate users are being blocked:
- Increase thresholds
- Change action from "block" to "challenge"
- Review security events to identify patterns