Skip to content

@ursalock/client

Passkey-based authentication client for ursalock.

Terminal window
npm install @ursalock/client

The main client for authentication and session management.

import { VaultClient } from "@ursalock/client";
const vaultClient = new VaultClient({
serverUrl: "https://vault.example.com",
});
OptionTypeRequiredDefaultDescription
serverUrlstringYes-Base URL of your ursalock server

Register a new user with a passkey.

import { useSignUp } from "@ursalock/client";
function SignUp() {
const { signUp, isLoading, error } = useSignUp(vaultClient);
const handleSignUp = async () => {
const result = await signUp({ usePasskey: true });
if (result.success) {
console.log("Registered!", result.credential);
// result.credential.jwt - auth token
// result.credential.cipherJwk - encryption key
} else {
console.error(result.error);
}
};
return (
<button onClick={handleSignUp} disabled={isLoading}>
{isLoading ? "Creating..." : "Sign Up with Passkey"}
</button>
);
}

Authenticate an existing user.

import { useSignIn } from "@ursalock/client";
function SignIn() {
const { signIn, isLoading, error } = useSignIn(vaultClient);
const handleSignIn = async () => {
const result = await signIn({ usePasskey: true });
if (result.success) {
console.log("Signed in!", result.credential);
}
};
return (
<button onClick={handleSignIn} disabled={isLoading}>
Sign In with Passkey
</button>
);
}

Check if WebAuthn is supported.

import { usePasskeySupport } from "@ursalock/client";
function AuthGate({ children }) {
const supportsPasskey = usePasskeySupport(vaultClient);
if (!supportsPasskey) {
return <p>Your browser doesn't support passkeys.</p>;
}
return children;
}

The credential object returned after authentication.

interface ZKCredential {
jwt: string; // Auth token for server requests
cipherJwk: CipherJWK; // Encryption key for vault
}

Short-lived auth token:

  • Stored in localStorage automatically
  • Used for server API requests
  • Refreshed automatically when expired

Encryption key derived from passkey:

  • Lives only in memory (not persisted)
  • Lost on page refresh (requires re-auth)
  • Used by @ursalock/zustand for encryption

Get the Authorization header for API requests.

const header = vaultClient.getAuthHeader();
// { "Authorization": "Bearer eyJ..." }
fetch("/api/protected", { headers: header });

Get the raw JWT token.

const token = vaultClient.getToken();
// "eyJ..." or null

Check if user has a valid JWT.

if (vaultClient.isAuthenticated()) {
// Has valid JWT (but cipherJwk may be null after refresh)
}

Clear the session.

await vaultClient.logout();
// Clears JWT from localStorage
const result = await signIn({ usePasskey: true });
if (!result.success) {
switch (result.error) {
case "NotAllowedError":
// User cancelled passkey prompt
break;
case "NotFoundError":
// No passkey for this site
break;
case "SecurityError":
// Not HTTPS
break;
case "InvalidStateError":
// Passkey already registered
break;
default:
console.error(result.error);
}
}
import type { ZKCredential, SignUpResult, SignInResult } from "@ursalock/client";
interface SignUpResult {
success: boolean;
credential?: ZKCredential;
error?: string;
}
interface SignInResult {
success: boolean;
credential?: ZKCredential;
error?: string;
}
interface ZKCredential {
jwt: string;
cipherJwk: CipherJWK;
}
import { useState } from "react";
import { VaultClient, useSignUp, useSignIn, usePasskeySupport, type ZKCredential } from "@ursalock/client";
const vaultClient = new VaultClient({
serverUrl: "https://vault.example.com",
});
function Auth({ onAuth }: { onAuth: (c: ZKCredential) => void }) {
const [mode, setMode] = useState<"signin" | "signup">("signin");
const [error, setError] = useState<string | null>(null);
const supportsPasskey = usePasskeySupport(vaultClient);
const { signUp, isLoading: isSigningUp } = useSignUp(vaultClient);
const { signIn, isLoading: isSigningIn } = useSignIn(vaultClient);
const isLoading = isSigningUp || isSigningIn;
if (!supportsPasskey) {
return <p>Passkeys not supported</p>;
}
const handleAuth = async () => {
setError(null);
const fn = mode === "signup" ? signUp : signIn;
const result = await fn({ usePasskey: true });
if (result.success && result.credential) {
onAuth(result.credential);
} else {
setError(result.error ?? "Authentication failed");
}
};
return (
<div>
{error && <p style={{ color: "red" }}>{error}</p>}
<button onClick={handleAuth} disabled={isLoading}>
{isLoading ? "..." : mode === "signup" ? "Sign Up" : "Sign In"}
</button>
<button onClick={() => setMode(m => m === "signin" ? "signup" : "signin")}>
{mode === "signin" ? "Create account" : "Sign in instead"}
</button>
</div>
);
}