Packages
Passkey
Getting started with @railman/auth-zero-access-passkey — enhancePasskey and browser PRF.
Getting started — Passkey
@railman/auth-zero-access-passkey hardens stock @better-auth/passkey (counter guard, pinned RP/origin, UV required) and ships browser PRF helpers for vault unlock.
Guide: Passkeys & PRF · API: API
Install
pnpm add @railman/auth-zero-access-passkey @better-auth/passkeyServer — enhancePasskey
import { betterAuth } from "better-auth";
import { passkey } from "@better-auth/passkey";
import { enhancePasskey } from "@railman/auth-zero-access-passkey";
const passkeyStack = enhancePasskey(passkey, {
rpID: "example.com",
origin: ["https://example.com"], // never trust request Origin
// Rung 0 — upgrade to a step-up assertion in production
assertCredentialAccess: "session",
});
export const auth = betterAuth({
plugins: [...passkeyStack.plugins], // [stock passkey, guard]
});
// Optional: hand controller to elevate
// elevate({ passkeyCounterGuard: passkeyStack.controller, … })enhancePasskey builds stock passkey(...) from frozen options — do not pass options to stock yourself.
Gradual adoption
| Rung | Option | Until then |
|---|---|---|
| Management | assertCredentialAccess: "session" | "deny" | fn | Required at construct |
| Graduate | securityLevel: "strict" | Standard keeps integration warnings |
Pin passkeyStack.posture in CI when production-ready.
The authenticated counter watermark uses the same root authority as the paired zeroAccess() plugin. Applications do not configure a separate passkey authority.
Browser — PRF config
import { defineZeroAccessPasskeyCryptoConfig } from "@railman/auth-zero-access-passkey/client";
const cryptoConfig = defineZeroAccessPasskeyCryptoConfig({
salt: process.env.NEXT_PUBLIC_PRF_SALT!, // tenant salt — not a server plugin option
});Client BA plugin
import { passkeyClient } from "@better-auth/passkey/client";
createAuthClient({ plugins: [passkeyClient()] });