@kaidn/nuxt
v2.0.0
Published
Kaidn for Nuxt. A composable that collects a device id in the browser, and a server helper that scores it with the IP and device-token cookie read straight off the h3 event.
Maintainers
Readme
@kaidn/nuxt
Kaidn for Nuxt. A composable that collects a device id in the browser, and a server helper that scores it with the IP and the device-token cookie read straight off the h3 event.
npm install @kaidn/nuxtFull guide: kaidn.io/docs/quickstart/nuxt
Why this exists
You can do all of this with @kaidn/fp and @kaidn/sdk directly. This package removes
the two things a hand-written server route gets subtly wrong.
The IP. getRequestIP(event) returns an address the server already resolved.
Written by hand this is the field people either forget — the event goes out with no
ip and every IP signal quietly dies — or get wrong by trusting x-forwarded-for
when nothing overwrites it.
The device token. Read from the cookie on the way in, written back on the way out. That is what turns a fingerprint which collides across people into a browser you actually recognise.
Client
The publishable key is an argument, not something this package reaches into Nuxt to
find. Importing useRuntimeConfig from Nuxt's virtual imports would save you one line
and tie this package to an internal that moves between versions. This works on Nuxt 3,
Nuxt 4, plain Vue, and in a test.
// app/composables/useKaidn.ts
import { useDeviceId } from "@kaidn/nuxt";
export function useKaidn() {
return useDeviceId(useRuntimeConfig().public.kaidnPk);
}<script setup lang="ts">
const { getDeviceId } = useKaidn();
const deviceId = await getDeviceId(); // never throws; null on the server or when blocked
</script>Collection is deduplicated for the life of the page: a double-clicked submit button does not fingerprint twice, and each beacon is a billable event.
Server
// server/api/signup.post.ts
import { createKaidn } from "@kaidn/nuxt/server";
const kaidn = createKaidn({ apiKey: process.env.NUXT_KAIDN_API_KEY! });
export default defineEventHandler(async (event) => {
const { email, deviceId } = await readBody(event);
const result = await kaidn.score(event, {
event: "signup",
email,
device_id: deviceId ?? undefined,
});
if (result.verdict === "block") {
throw createError({ statusCode: 403, statusMessage: "Signup refused" });
}
return { ok: true };
});No ip, no device_token: both come from the event. Anything you pass wins over what is
inferred.
A server route is a public endpoint. Anyone can POST it without loading your form, so enforce the verdict inside the handler.
Options
| option | default | |
|---|---|---|
| apiKey | — | required, and every other @kaidn/sdk option is accepted too |
| cookieName | __kdn | the first-party device-token cookie |
| cookieMaxAge | one year, in seconds | |
| persistToken | true | set false to stop refreshing the cookie |
There is deliberately no option to trust x-forwarded-for. h3 can be told to read it,
and h3's own docs say to make sure the header can be trusted first — its first entry is
client-supplied, so unless something you control overwrites it on every request, a caller
can choose their own address. A fraud library offering that as a switch is offering a
footgun. If you genuinely need it, pass ip yourself and it wins.
Versions
h3 is a peer dependency on the 1.x line, which is what Nuxt ships. It is a peer
rather than a dependency so there is exactly one h3 in the process: yours.
License
MIT
