Auth & Permissions

Local-first auth

Start offline with a recoverable account, then link an external identity without changing ownership.

A local-first account lets someone start using an app before signing up. Jazz derives its founding identity and account from a random signing root. Creating that account works offline. When it connects, the core verifies the signing proof and admits the deterministic account.

Client setup

Configure a session once:

import { createJazzSession } from "jazz-tools/client";

const jazz = await createJazzSession({
  appId: "my-app",
  serverUrl: "https://core.example",
  initial: "local-first",
});

Session creation prepares crypto, restores usable saved selection, or creates a local-first account. Browser sessions retain signing roots in browser storage. Native hosts use an atomic, protected AccountStore; provider JWTs are never persisted in it. Creating a local-first account works offline once runtime assets are available.

Framework JazzSessionProvider and session hooks expose the same lifecycle. Account commands run outside contexts after ordinary graceful shutdown, which Jazz coordinates automatically. See Lifecycle. Lower-level account handles and client factories remain available when an app deliberately owns its own context lifecycle.

Backing up and restoring the secret

A signing root is a credential. Possession allows acting as its local-first identity. Jazz represents it as jazz-auth-v1: followed by 43 unpadded base64url characters encoding 32 random bytes. Export it explicitly from a live local-first handle:

import { exportLocalFirstSecret } from "jazz-tools";
import { RecoveryPhrase } from "jazz-tools/passphrase";

const secret = exportLocalFirstSecret(jazz.getSnapshot().account!);
const phrase = RecoveryPhrase.fromSecret(secret);

The recovery phrase encodes the same root; it is not a second password. Keep it out of logs and ordinary account-state UI. Export fails for external or logged-out handles. Signing roots remain retained after logout so explicit recovery can restore them.

Recovery passphrase

Restore through the session; it owns the graceful handoff:

await jazz.restoreLocalFirst(RecoveryPhrase.toSecret(userInput));

Session account transitions wait for pending writes to sync before replacing the client. Ordinary jazz.close() preserves local durability without requiring online sync.

Passkey backup

Browser apps can wrap the same exported root with a passkey:

import { BrowserPasskeyBackup } from "jazz-tools/passkey-backup";

const backup = new BrowserPasskeyBackup({
  appName: "My App",
  appHostname: "app.example",
});
await backup.backup(exportLocalFirstSecret(jazz.getSnapshot().account!), "My account");

await backup.restore() returns the root for await jazz.restoreLocalFirst(secret). Pin the canonical production hostname when configuring a passkey backup; passkey availability and synchronization depend on the user's platform.

Linking an external provider

An external provider can issue an ordinary JWT with iss and sub. It does not need to mint Jazz-specific claims or reuse the local-first subject.

Call await jazz.linkJWT({ getToken }); Jazz performs graceful shutdown, linking, and client replacement. The external identity must be fresh in this application's registry. Linking requires a connection to the core and keeps the existing account ID, so account-based ownership remains valid. Earlier rows retain the exact identity that authored them.

The core never moves an already assigned identity to another account. If the provider identity already has an account, the app must choose whether to log into that account or migrate data explicitly. See Authentication for registration versus login, and Lifecycle for failure and logout handling.

Permissions by auth mode

Use session.user.account for stable account ownership. Use the exact issuer and subject when a policy deliberately distinguishes linked identities. A local-first identity is authenticated by its signing key; it is not an unauthenticated guest.

Provider claims can add application requirements, such as a verified role. They cannot replace the account assignment. Define those checks in Permissions, alongside ownership policies.

On this page