Skip to content

client.auth

Authentication is exposed through client.auth.zk. See the Authentication guide for usage.

interface AuthUser {
username: string;
/** Decimal-string Poseidon commitment — the user's stable id. */
commitment: string;
}
register(params: {
username: string;
password: string;
email?: string | null;
login?: boolean; // default: true
}): Promise<AuthUser>

Derives the identity, registers the commitment, and (unless login: false) signs in. Throws if the username is taken.

login(username: string, password: string, opts?: { rememberMe?: boolean }): Promise<AuthUser>

Re-derives the identity, proves it against a fresh server challenge, and establishes the session and identity. rememberMe: true requests a longer-lived session. Throws on bad credentials / expired challenge.

restore(): Promise<AuthUser | null>

Validates a persisted session token (from the sessionStore). Returns the user when valid, or null (clearing a stale token). Does not unlock the identity — call unlock() for encryption/messaging.

unlock(password: string): Promise<void>

Re-derives the identity for an already-authenticated (restored) session. Verifies the password by matching the derived commitment to the session’s; throws on mismatch.

enrollPasskey(opts?: { rpId?: string; rpName?: string; label?: string }): Promise<void>

Adds a WebAuthn passkey (PRF) that wraps the master seed. Signed-in only.

loginWithPasskey(username: string, opts?: { rememberMe?: boolean }): Promise<AuthUser>

Passwordless sign-in via an enrolled passkey. Unwraps the seed and re-derives the identity, establishing session and identity.

passkeyAvailable(): boolean

Whether WebAuthn is usable in this browser (sync).

passkeyPrfAvailable(): Promise<boolean | null>

Whether the authenticator supports the required PRF extension. null means it can’t be determined. Gate the passkey UI on a true result.

enrollRecoveryPhrase(): Promise<string>

Returns a 24-word BIP39 phrase to display once — it is the seed, nothing is stored server-side. Deterministic, so it returns the same words each call. Signed-in only.

recoverWithPhrase(username: string, mnemonic: string): Promise<AuthUser>

“Forgot password” path: re-derives the identity from the phrase and signs in. Follow with changePassword() to set a new password.

enrollEmailFactor(email: string): Promise<{
email: string;
confirm: (code: string) => Promise<void>;
}>

Add an email as a recovery + login factor. Sends a one-time code to the address and returns a confirm continuation — call it with the code from the inbox to finish enrolling:

const { confirm } = await client.auth.zk.enrollEmailFactor("me@example.com");
await confirm(codeFromInbox);

Signed-in only (the seed must be held). Verification-gated: the server only releases the split-key OPRF eval after the address is OTP-verified. Disclose the custody trade-off (recoverability vs. custody) in your enroll UI.

recoverWithEmail(username: string, opts?: { rememberMe?: boolean }): Promise<{
confirm: (code: string) => Promise<AuthUser>;
}>

“Forgot password” path via an enrolled email factor. Sends a code to the stored address (the response never reveals whether one exists), then confirm(code) unwraps the seed and signs in. Follow with changePassword().

const { confirm } = await client.auth.zk.recoverWithEmail("alice");
const user = await confirm(codeFromInbox);
enrollGoogleFactor(idToken: string): Promise<void>

Link a Google account as a recovery + login factor. idToken is a fresh Google ID token obtained via Google Identity Services (the SDK never renders Google UI). Signed-in only.

loginWithGoogle(username: string, idToken: string, opts?: { rememberMe?: boolean }): Promise<AuthUser>

Sign in with Google — verifies the ID token, unlocks the seed via the gated split-key eval, and establishes session and identity. The account must have linked the factor first (enrollGoogleFactor). Serves both “Sign in with Google” and “recover with Google”.

registerWithGoogle(username: string, idToken: string): Promise<AuthUser>

Register a brand-new, passwordless account whose only factor is Google. A random master seed is generated and wrapped under the Google-gated key; the user can add a password / passkey / phrase later. ZK registration is identical to a password signup.

changePassword(newPassword: string): Promise<void>

Re-wraps the unchanged seed under a new password. Identity and commitment never move. Signed-in only.

listFactors(): Promise<Array<{
id: string;
type: "password" | "passkey" | "phrase-marker" | "email" | "google";
label?: string;
createdAt?: number;
masked?: string;
}>>

Lists the user’s enrolled login methods (metadata only). Gated factors (email / google) carry a masked display hint (e.g. "m•••@gmail.com"); the full value is never returned.

removeFactor(id: string): Promise<void>

Removes an enrolled factor. Throws if it’s the last remaining one.

recover(): Promise<boolean>

Silently re-mints a session token by re-proving the in-memory identity — no password prompt. Resolves true if it succeeded, false if it can’t (the identity isn’t in memory, e.g. after a reload that kept only the token). The client calls this automatically when a request hits 401; you normally use the client-level recoverSession() + onSessionExpired instead of calling it directly.

logout(): Promise<void>

Clears the session, identity, and persisted token.

GetterTypeDescription
client.auth.zk.userAuthUser | nullCurrent user (sync).
client.auth.zk.tokenstring | nullCurrent session token (sync).
client.auth.zk.identityZkIdentity | nullDerived identity, or null when locked.
client.auth.zk.seedBase64string | nullMaster seed as base64, or null when locked. Wrap per-user app data to this so it survives password changes and passkey login.
client.auth.userAuthUser | nullConvenience alias.
import { VaultUnavailableError } from "@muhkoo/connect";
  • VaultUnavailableError — thrown by login / unlock when the per-user vault is unreachable (network / 5xx / rate-limit). Distinct from a wrong password — tell the user to retry rather than re-enter credentials.

Centralized hosted auth on auth.muhkoo.dev (authorization-code + PKCE). See the Hosted authentication guide.

  • login({ appId, redirectUri })Promise<string>. Stashes a PKCE verifier + state and redirects the browser to the hosted /authorize page; resolves to the URL (also returned for popup/test use). redirectUri must be registered for the app (portal → App Detail → Hosted sign-in).
  • handleCallback()Promise<{ username, commitment }>. On your callback route: verifies state, exchanges the code for a session, unseals the master seed from the URL fragment, establishes the session + identity, and scrubs the URL. Throws on a bad/expired code or PKCE/CSRF mismatch.
  • isCallback()boolean. true when the current URL is a hosted-auth callback (?code&state). Use it to guard handleCallback().
  • manageAccount({ returnUri? })Promise<string>. Redirect to centralized account & security management (auth.muhkoo.dev/security), where the user manages passkeys, recovery email, Google, recovery phrase, and password. returnUri (default: the current URL) becomes a “Back to app” button. Wire it to a “Manage account” / settings entry in your app — there’s no per-app security UI to build.

Set authBaseUrl on the Client to override the hosted origin (default auth.muhkoo.dev).