Zero Accessby Railmandocs
Compose

Step-up only

Password and TOTP sudo without passkeys, vault, or chat.

Step-up only

Use this when you want GitHub-style sudo / re-auth on an existing Better Auth session — delete account, change billing, open an admin panel — and you do not need WebAuthn, a vault, or sealed chat.

Elevate never unlocks a MEK. There is no PRF or recovery phrase on this path.

Live: Elevate lab · Chooser: Use cases

What you install

PackageRole
@railman/auth-login-factorSigned L0 session stamps + requireAccess maps
@railman/auth-elevateStep-up verify + short elevate claim on the session
better-auth/plugins twoFactorOptional TOTP enroll/storage (elevate verifies)

Skip @better-auth/passkey, @railman/auth-zero-access-passkey, zeroAccess, zero-vault, and zero-e2e.

Server composition

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

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

Omit passkeyCounterGuard (or set allowPasskey: false) so passkey step-up stays off.

Typechecked source: docs-site/verified-examples/dx-a-step-up-only.ts (pnpm docs:verify-examples).

Gate a privileged route

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,
});

For maps that also check elevate AMR and distinct-from-login rules, use requireAccess + createElevateOpener — see Login factor and API — privilege helpers.

Client flow

  1. GET /elevate/methods — which factors are available
  2. POST /elevate/verify with { method: "password", password } or { method: "totp", totpCode }
  3. Session row carries the signed claim; UI may show elevatedAt / expiresAt only

HTTP table: API — Elevate.

Hard rules

  • Elevate (L1) vault unlock (L2)
  • Authorize from the server session, not client-forged claims
  • Keep session.cookieCache disabled when using login-factor stamps

Next

Demo-only: deploymentMode: "single-instance". Shared stores for multi-node: Multi-instance.

On this page