Reference

Local-first auth internals

How a local signing identity founds an offline account and links ordinary provider identities through the core registry.

Local-first auth separates a signing identity from the account that owns application data. A local-first account can be created offline; adding an external identity requires the core registry.

Identity and account derivation

The client retains a 32-byte secret, represented as jazz-auth-v1:<43 unpadded base64url characters>. Native crypto derives an Ed25519 signing key using the jazz-auth-sign-v1 domain separator. Its public key determines the local identity's UUID subject in the jazz-auth-key-v1 namespace.

The identity is (urn:jazz:local-first, subject). Its founder account is a separate, application-scoped UUIDv5: the namespace is the normalized app UUID, and the name is jazz-account-founder-v1 followed by a zero byte and the subject. Therefore the same recovery secret restores the same identity and the same account within an application. A new secret creates a different identity and founder account.

createAccountManager prepares native crypto and retained selection. After preparation, createLocalFirst() synchronously returns an opaque AccountHandle. A context waits for the handle's recovery material to be durably retained before using its credentials. Browser, React Native, and Expo adapters provide host storage; the account-selection logic is shared.

Self-signed authentication

Local-first JWTs use Ed25519, iss: "urn:jazz:local-first", the derived identity subject, an application audience, and the embedded jazz_pub_key. The server verifies the signature, checks the audience and token lifetime, and confirms that the public key derives the claimed subject.

This proves control of the identity without an external JWKS provider. The deterministic founder rule lets core admit that identity's account without a competing registration choice. It does not let the caller choose an arbitrary account ID.

The core registry records the exact identity-to-account assignment. External identities require explicit registration or linking; merely verifying an external JWT does not create an account.

Linking a provider identity

The provider keeps its normal issuer and subject. It does not rewrite its user ID, accept a Jazz-specific signup proof, or mint custom linking claims.

The app first gracefully closes its current context with shutdown({ waitForSync: true }). If synchronization fails, it keeps that context and retries later. Once closed, it calls accounts.linkJWT({ getToken }) outside any context, then opens a new context using the returned handle.

The core linking protocol uses a nonce and two authenticated proofs: a currently permitted identity authorizes linking a particular target identity, and that target proves control through its ordinary JWT. The ordered registry accepts the link only if the target identity is unassigned. An identity already assigned to another account cannot be moved or merged, including after revocation.

Existing rows retain their original structured authorship. Their account IDs stay unchanged, while new writes record the linked provider identity. Policies comparing .account keep ownership stable; comparisons of the entire author also distinguish the acting identity.

If linking fails after the old context was closed, the app may reopen the account still selected by the manager. The manager performs no database shutdown or upload scheduling itself. See Local-first auth for the application flow.

On this page