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
| Name | Description |
|---|---|
| DeviceSecretAuth | EvoluAppAuth with a DeviceSecret the app keeps where only the user or the platform can unlock it, such as Electron's safeStorage. |
| DeviceSecretKeyProvider | A 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. |
| DeviceSecretKeyRefusedError | A 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. |
| DeviceSecretKeyUnavailableError | A DeviceSecretKeyProvider can't provide a key, such as for a passkey whose provider lacks the WebAuthn PRF extension. |
| DeviceSecretUnwrapError | A 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. |
| InstantStartAuth | EvoluAppAuth 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. |
| WrappedDeviceSecretNotPersistedError | The 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. |
| WrappedDeviceSecretStoredError | forgetWrappedDeviceSecret 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. |
| DeviceSecretKeyError | An error of a DeviceSecretKeyProvider. |
| DeviceSecretKeyId | The id of a key that wraps a DeviceSecret, such as a passkey's credential ID, from 1 to 1023 bytes as WebAuthn allows. |
| EvoluAppAuth | How 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. |
| WrappedDeviceSecret | A 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. |
| DeviceSecretKeyId | The id of a key that wraps a DeviceSecret, such as a passkey's credential ID, from 1 to 1023 bytes as WebAuthn allows. |
| WrappedDeviceSecret | A 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. |
| wrappedDeviceSecretEvoluSchema | Evolu'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. |
| wrappedDeviceSecretTable | The name of Evolu's table of an app's WrappedDeviceSecret. Permanent once shipped. |
| unwrapDeviceSecret | Authenticates and unwraps a WrappedDeviceSecret. |
| wrapDeviceSecret | Wraps a DeviceSecret with a key from a DeviceSecretKeyProvider. A fresh random nonce makes every wrap of the same secret different. |
Device
| Name | Description |
|---|---|
| DeviceSecretError | Error 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. |
| DeviceSecret | The 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. |
| appOwnerTable | The name of Evolu's AppOwner table. Permanent once shipped. |
| deviceEvoluSchema | Evolu's tables of the device database. Its table and column names are permanent once shipped. |
| DeviceSecret | The 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. |
| createDeviceSecret | Creates a cryptographically random DeviceSecret. |