Zero Accessby Railman
Compose

Passkey + step-up

Hardened WebAuthn login plus elevate that shares the passkey counter watermark.

Passkey + step-up

Use this when users sign in with passkeys and you also need sudo / re-auth (passkey, TOTP, or password). Login and elevate share one counter controller so watermarks stay consistent.

Still no vault and no chat on this path. elevate.withPasskey() is sudo, not PRF unlock — Client boundaries.

Live: Elevate lab · Chooser: Use cases

What you install

Everything from Passkey login, plus:

PackageRole
@railman/auth-login-factorL0 stamps + requireAccess
@railman/auth-elevateStep-up; passkeyCounterGuard: passkeyStack.controller
twoFactorOptional TOTP enroll/storage

Server composition

import { betterAuth } from "better-auth";
import { passkey } from "@better-auth/passkey";
import { twoFactor } from "better-auth/plugins";
import { enhancePasskey } from "@railman/auth-zero-access-passkey";
import { elevate } from "@railman/auth-elevate";
import { loginFactors } from "@railman/auth-login-factor";

const passkeyStack = enhancePasskey(passkey, {
  rpID: "example.com",
  origin: ["https://example.com"],
  // Lab rung — production: custom assert after elevate / sudo
  assertCredentialAccess: "session",
});

export const auth = betterAuth({
  secret: process.env.BETTER_AUTH_SECRET!,
  session: { cookieCache: { enabled: false } },
  plugins: [
    ...passkeyStack.plugins,
    loginFactors(),
    twoFactor(),
    elevate({
      passkeyCounterGuard: passkeyStack.controller,
      passwordOnlyIfNoStronger: true,
      elevatedTtlSec: 300,
      maxElevatedSec: 3600,
    }),
  ],
});

Drop the elevate({…}) block (and usually loginFactors / twoFactor) if you only need Passkey login.

Typechecked source: docs-site/verified-examples/dx-bc-passkey-elevate.ts.

Authorize privileged actions

Bare elevate presence:

import { requireElevate } from "@railman/auth-elevate";

await requireElevate(session.session.elevateClaim, {
  secret: process.env.BETTER_AUTH_SECRET!,
  userId: session.user.id,
  sessionToken: session.session.token,
  ttlSec: 300,
});

AMR / distinct-from-login maps (AUTH_REQUIRE_PRESETS.privilegedAction, elevateSudo, …) need createElevateOpener bound to the same user and session token:

import {
  AUTH_REQUIRE_PRESETS,
  createElevateOpener,
  requireAccess,
} from "@railman/auth-login-factor";

const secret = process.env.BETTER_AUTH_SECRET!;

await requireAccess({
  secret,
  session: {
    userId: session.user.id,
    sessionToken: session.session.token,
    loginFactor: session.session.loginFactor,
    elevateClaim: session.session.elevateClaim,
  },
  map: AUTH_REQUIRE_PRESETS.privilegedAction,
  elevateOpener: createElevateOpener({
    secret,
    userId: session.user.id,
    sessionToken: session.session.token,
    ttlSec: 300,
    maxElevatedSec: 3600,
  }),
  // enrolled: { passkey, totp, password } — server lookup for password AMR
});

Details: Login factor · API — privilege helpers.

Client flow

Same elevate HTTP as Step-up only, plus passkey assertion when that method is available. See API — Elevate.

Next

Pass credential management from "session" to a step-up assertion before production. See gradual security on Passkey login.

On this page