Bot & Abuse Protection
An allow / challenge / deny pipeline over IP allow-deny lists, a CAPTCHA hook, and a bot classifier — the self-hosted framework half of WorkOS "Radar" (which is hosted-only). Composes with the adaptive risk engine.
#The guard
createAbuseGuard owns the decision pipeline; you supply the signals:
ipDeny / ipAllowIP lists, exact or IPv4 CIDR.
verifyCaptchaA hook wrapping your CAPTCHA provider.
classifyBotThe built-in UA heuristic, or your own AI-agent detector.
Honest scope: fingerprint-grade detection needs a data network — this is the framework and hooks.
TS
import {
createAbuseGuard,
defaultBotClassifier,
verifyTurnstile
} from '@absolutejs/auth';
// The decision pipeline: IP allow/deny (exact or IPv4 CIDR) + a CAPTCHA hook +
// a bot classifier -> allow / challenge / deny. The framework half of WorkOS
// "Radar" (which is hosted-only); you supply the signals.
export const abuse = createAbuseGuard({
// a built-in adapter (Turnstile/reCAPTCHA/hCaptcha) or your own (token) => boolean
verifyCaptcha: verifyTurnstile({ secret: process.env.TURNSTILE_SECRET }),
// built-in User-Agent heuristic, or pass your own (e.g. an AI-agent detector)
classifyBot: defaultBotClassifier,
ipDeny: ['203.0.113.0/24'],
captchaAction: 'challenge' // default 'deny'
});#CAPTCHA adapters
Built-in verifyTurnstile, verifyRecaptcha (with an optional v3 minScore), and verifyHcaptcha wrap each provider's siteverify call (the client IP is taken from the assess context). Drop one into verifyCaptcha — no extra plumbing.
TS
import {
createAbuseGuard,
verifyHcaptcha,
verifyRecaptcha,
verifyTurnstile
} from '@absolutejs/auth';
// Built-in CAPTCHA adapters call the provider's siteverify for you (remoteip is
// taken from the assess context). All three share the same shape; reCAPTCHA v3
// also takes a minScore.
const turnstile = verifyTurnstile({ secret: process.env.TURNSTILE_SECRET });
const recaptcha = verifyRecaptcha({ minScore: 0.5, secret: process.env.RECAPTCHA_SECRET });
const hcaptcha = verifyHcaptcha({ secret: process.env.HCAPTCHA_SECRET });
const abuse = createAbuseGuard({ verifyCaptcha: turnstile, captchaAction: 'deny' });#Wire into login
Call assess at the top of register / login with the request context. The result is the most severe action that fired plus every reason; an allow-listed IP short-circuits.
TS
// Run it at the top of register / login, where the request context is available.
// Pairs with the adaptive risk engine — run both.
const { action, reasons } = await abuse.assess({
captchaToken: body.captchaToken,
ip: request.headers.get('x-forwarded-for') ?? undefined,
userAgent: request.headers.get('user-agent') ?? undefined
});
if (action === 'deny') return status('Forbidden', { reasons });
if (action === 'challenge') return status('Bad Request', 'captcha_required');
// action === 'allow' -> proceed with the credential work