API reference › @evolu/common › local‑first/Auth

Local-first auth: how createEvoluApp unlocks an app's device database, which keeps the AppOwners the app can open on this device, and Evolu's AppOwner table there.

Only Evolu writes the AppOwner table; apps read and change it through EvoluApp. The table is local-only: its rows never sync, keep no history, and a delete removes the row. A row's id is the OwnerId its secret derives, so storing the same secret again updates the same row. Its ownerId system column is the device owner.

See docs/adr/evolu-creates-and-stores-app-owners.md.

Auth

NameDescription
DeviceSecretAuthEvoluAppAuth with a DeviceSecret the app keeps where only the user or the platform can unlock it, such as Electron's safeStorage.
DeviceSecretKeyProviderA source of the key that wraps an app's DeviceSecret, such as a passkey's PRF output, which @evolu/web provides. Creating or getting a key may prompt the user, which some browsers allow only in a user gesture. Its Tasks must end when their Run aborts them, such as by passing its signal to the prompt, because a setup holds the app's lock while it creates or gets a key, so a Task that keeps waiting holds up every tab's unlock. See unlockWrappedDeviceSecret.
DeviceSecretKeyRefusedErrorA DeviceSecretKeyProvider refused to create or get a key, such as when the user dismissed its prompt or the key is gone. It can be temporary, such as a locked password manager that took the prompt over, so let the user try again.
DeviceSecretKeyUnavailableErrorA DeviceSecretKeyProvider can't provide a key, such as for a passkey whose provider lacks the WebAuthn PRF extension.
DeviceSecretUnwrapErrorA WrappedDeviceSecret failed authentication in unwrapDeviceSecret: the key is another one, or the bytes changed. Or it holds 32 zero bytes, instant start's public secret, which isn't a DeviceSecret.
InstantStartAuthEvoluAppAuth without a prompt: the device database's key is public, so anything that can read the app's storage can read the AppOwner secrets kept there. The app database stays encrypted with its AppOwner.
WrappedDeviceSecretNotPersistedErrorThe platform keeps nothing, such as Safari's Private Browsing, so unlockWrappedDeviceSecret creates no key: every start would create another one, such as another passkey in the user's password manager. See DevicePersistence.
WrappedDeviceSecretStoredErrorforgetWrappedDeviceSecret with onlyPending found a stored WrappedDeviceSecret rather than a setup's pending key, so it forgot nothing. Forgetting a stored secret loses its device database, so the app asks the user to confirm and then forgets it without onlyPending.
DeviceSecretKeyErrorAn error of a DeviceSecretKeyProvider.
DeviceSecretKeyIdThe id of a key that wraps a DeviceSecret, such as a passkey's credential ID, from 1 to 1023 bytes as WebAuthn allows.
EvoluAppAuthHow createEvoluApp unlocks the app's device database, which keeps the AppOwners the app can open on this device. Fixed per app, like EvoluConfig.appName: another one opens another device database.
WrappedDeviceSecretA DeviceSecret wrapped by a key from a DeviceSecretKeyProvider: a 24-byte nonce, the 32-byte ciphertext, and its 16-byte authentication tag. The format is permanent once shipped.
DeviceSecretKeyIdThe id of a key that wraps a DeviceSecret, such as a passkey's credential ID, from 1 to 1023 bytes as WebAuthn allows.
WrappedDeviceSecretA DeviceSecret wrapped by a key from a DeviceSecretKeyProvider: a 24-byte nonce, the 32-byte ciphertext, and its 16-byte authentication tag. The format is permanent once shipped.
wrappedDeviceSecretEvoluSchemaEvolu's tables of the database that holds an app's WrappedDeviceSecret, whose key is public, because it must open before the DeviceSecret exists. Its table and column names are permanent once shipped.
wrappedDeviceSecretTableThe name of Evolu's table of an app's WrappedDeviceSecret. Permanent once shipped.
unwrapDeviceSecretAuthenticates and unwraps a WrappedDeviceSecret.
wrapDeviceSecretWraps a DeviceSecret with a key from a DeviceSecretKeyProvider. A fresh random nonce makes every wrap of the same secret different.

Device

NameDescription
DeviceSecretErrorError for 32 zero bytes, instant start's public secret, which isn't a DeviceSecret. It leaves the value out, as a secret's error should.
DeviceSecretThe secret of an app's device database, which keeps the AppOwners the app can open on this device. Keep it where only the user or the platform can unlock it, as loadDeviceSecret of @evolu/react-native does, or in Electron's safeStorage. It isn't an OwnerSecret: createEvoluApp derives the device database's owner from it on paths no OwnerSecret reaches, so no AppOwner's ID or keys name or open the device database. Create it with createDeviceSecret and never use its bytes as an OwnerSecret: an AppOwner's mnemonic is its secret, so whoever holds it would derive the device database's key. 32 zero bytes are instant start's public secret, so the Type rejects them, also in createEvoluApp, which checks the secret it gets with the Type.
appOwnerTableThe name of Evolu's AppOwner table. Permanent once shipped.
deviceEvoluSchemaEvolu's tables of the device database. Its table and column names are permanent once shipped.
DeviceSecretThe secret of an app's device database, which keeps the AppOwners the app can open on this device. Keep it where only the user or the platform can unlock it, as loadDeviceSecret of @evolu/react-native does, or in Electron's safeStorage. It isn't an OwnerSecret: createEvoluApp derives the device database's owner from it on paths no OwnerSecret reaches, so no AppOwner's ID or keys name or open the device database. Create it with createDeviceSecret and never use its bytes as an OwnerSecret: an AppOwner's mnemonic is its secret, so whoever holds it would derive the device database's key. 32 zero bytes are instant start's public secret, so the Type rejects them, also in createEvoluApp, which checks the secret it gets with the Type.
createDeviceSecretCreates a cryptographically random DeviceSecret.