Packages
Elevate
Getting started with @railman/auth-elevate — step-up / sudo for Better Auth.
Getting started — Elevate
@railman/auth-elevate adds a short step-up window on an existing session. It does not unlock vault material.
Demo: /elevate · Guide: Elevate and sudo · HTTP: API
Install
pnpm add @railman/auth-elevate @railman/auth-login-factor
# optional TOTP storage:
# better-auth twoFactor plugin
# optional passkey step-up:
# @better-auth/passkey @railman/auth-zero-access-passkeyMinimal — password / TOTP only
import { betterAuth } from "better-auth";
import { twoFactor } from "better-auth/plugins";
import { elevate, requireElevate } from "@railman/auth-elevate";
import { loginFactors } from "@railman/auth-login-factor";
export const auth = betterAuth({
plugins: [
loginFactors(),
twoFactor(),
elevate({
// omit passkeyCounterGuard → passkey step-up off
passwordOnlyIfNoStronger: true,
elevatedTtlSec: 300,
}),
],
});Gate a route:
await requireElevate(session.session.elevateClaim, {
userId: session.user.id,
sessionToken: session.session.token ?? session.session.id,
secret: process.env.BETTER_AUTH_SECRET!,
ttlSec: 300,
});With passkey step-up
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"],
assertCredentialAccess: "session",
});
elevate({
passkeyCounterGuard: passkeyStack.controller,
passwordOnlyIfNoStronger: true,
elevatedTtlSec: 300,
});Client
elevateClient is L1 ceremony HTTP. It does not unlock a vault, persist wraps, enroll TOTP/passkeys, or authorize from its JSON. Full split: Client boundaries.
import { elevateClient } from "@railman/auth-elevate/client";
import { createAuthClient } from "better-auth/client";
const authClient = createAuthClient({ plugins: [elevateClient()] });
await authClient.elevate.methods();
await authClient.elevate.verify({ method: "password", password });
await authClient.elevate.verify({ method: "totp", totpCode });
await authClient.elevate.withPasskey(); // existing session; not login; not PRF unlockReturned verify JSON is UI-only. Authorize with requireElevate / requireAccess on the server session.
Key options
| Option | Default intent |
|---|---|
elevatedTtlSec | 300 |
passwordOnlyIfNoStronger | true at mint (optional). Prefer action gates (amrAnyOf, privilegedAction) so password can mint while vault rejects it. |
passkeyCounterGuard | Optional — omit to disable passkey method |
| Storage | Better Auth database + secondaryStorage (not a plugin option) |
Next
- Client boundaries — elevate vs vault vs zero-access
- Use cases A / C
- Step-up only · Passkey + step-up
- API — Elevate