Zero Accessby Railmandocs
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/passkey

Server — 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

RungOptionUntil then
ManagementassertCredentialAccess: "session" | "deny" | fnRequired at construct
GraduatesecurityLevel: "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()] });

Next

On this page