Better Auth
Better Auth lets you run code before any auth endpoint with hooks.before. This recipe checks /sign-up/email requests. See the Better Auth hooks docs.
// auth.ts
import { betterAuth } from "better-auth";
import { createAuthMiddleware, APIError } from "better-auth/api";
async function burnguard(body: Record<string, unknown>) {
try {
const res = await fetch(`${process.env.BURNGUARD_URL}/v1/check`, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.BURNGUARD_SECRET_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify(body),
signal: AbortSignal.timeout(1500),
});
return res.ok ? await res.json() : { decision: "allow" };
} catch {
return { decision: "allow" };
}
}
export const auth = betterAuth({
// ...your database and emailAndPassword config
hooks: {
before: createAuthMiddleware(async (ctx) => {
if (ctx.path !== "/sign-up/email") return;
const check = await burnguard({
event: "signup",
email: ctx.body?.email,
ip: ctx.headers?.get("x-forwarded-for")?.split(",")[0]?.trim(),
user_agent: ctx.headers?.get("user-agent") ?? undefined,
device_token: ctx.body?.deviceToken, // send it from your client with the signup call
});
if (check.decision === "block") {
throw new APIError("FORBIDDEN", { message: "We couldn't create this account." });
}
// For "limit" and "review", remember the decision (e.g. in your own table keyed by email)
// and apply it when you grant free credits after the user is created.
}),
},
});
On the client, pass the token with the signup call:
const deviceToken = await (window as any).BurnGuard?.token().catch(() => undefined);
await authClient.signUp.email({ email, password, name, deviceToken } as any);
See Decisions and credit gating for how to handle limit and review.