[API reference](https://evolu.dev/docs/api-reference) › [@evolu/common](https://evolu.dev/docs/api-reference/common) › [local‑first/Evolu](https://evolu.dev/docs/api-reference/common/local-first/Evolu) › unlockWrappedDeviceSecret

```ts
function unlockWrappedDeviceSecret(__namedParameters: {
  appName: string & Brand<"UrlSafeString"> & Brand<"AppName">;
  keyProvider: DeviceSecretKeyProvider;
}): Task<
  Uint8Array<ArrayBufferLike> &
    Brand<"Entropy"> &
    Brand<"Length32"> &
    Brand<"DeviceSecret">,
  | DeviceSecretKeyError
  | DeviceSecretUnwrapError
  | WrappedDeviceSecretNotPersistedError,
  CreateDbWorkerDep &
    CreateBroadcastChannelDep &
    CreateMessageChannelDep &
    DevicePersistenceDep &
    LockManagerDep &
    ReloadAppDep &
    Partial<ConsoleDep> &
    Partial<CreateMemoryInstanceWorkerDep> &
    Partial<FlushSyncDep> &
    Partial<RequestPersistentStorageDep> &
    StoredWorkerDep
>;
```

Defined in: [packages/common/src/local-first/Evolu.ts:2980](https://github.com/evoluhq/evolu/blob/aa223771feea747ffb1b0f5f3271574455633e37/packages/common/src/local-first/Evolu.ts#L2980)

Unlocks the app's [DeviceSecret](https://evolu.dev/docs/api-reference/common/local-first/Auth/variables/DeviceSecret) with a key from the
[DeviceSecretKeyProvider](https://evolu.dev/docs/api-reference/common/local-first/Auth/interfaces/DeviceSecretKeyProvider), and on the first use creates them: a key, a
DeviceSecret, and its [WrappedDeviceSecret](https://evolu.dev/docs/api-reference/common/local-first/Auth/variables/WrappedDeviceSecret), which Evolu stores in a
database of its own whose key is public. Pass the DeviceSecret to
[createEvoluApp](https://evolu.dev/docs/api-reference/common/local-first/Evolu/functions/createEvoluApp) as [DeviceSecretAuth](https://evolu.dev/docs/api-reference/common/local-first/Auth/interfaces/DeviceSecretAuth).

On the first use, it runs under a lock per app, so a second tab waits and
then unlocks what the first one created. Once a wrapped secret is stored, it
only unlocks, without the lock, so no tab waits for another tab's prompt, and
an error, such as a dismissed prompt, never starts another setup, which would
orphan the device database. A setup that stopped after creating its key
resumes with that key, also after the provider refused it, so the user gets
no second passkey; only when the provider can't provide it does the next
setup create another one. When its database is refused, as `evoluError`
reports, it fails with an [UnknownError](https://evolu.dev/docs/api-reference/common/Error/variables/UnknownError), the refusal itself or one
wrapping it. Like `createEvoluApp`, it stays pending while another build of
the app keeps it waiting or the shared worker fails to load. Some browsers
allow a prompt only in a user gesture, so call it from one, such as a button
click, and use [hasWrappedDeviceSecret](https://evolu.dev/docs/api-reference/common/local-first/Evolu/functions/hasWrappedDeviceSecret) to tell which button to show.
After an error, keep the app usable and let the user try again, rather than
blocking it on the prompt.

WebAuthn reports a deleted passkey like a dismissed prompt, and a stored
secret whose key is gone for good fails every unlock, so after any refusal an
app can offer to start over with [forgetWrappedDeviceSecret](https://evolu.dev/docs/api-reference/common/local-first/Evolu/functions/forgetWrappedDeviceSecret). Its
`onlyPending` mode forgets a setup's pending key, which loses nothing and
needs no warning, and fails when it finds a stored secret, whose loss the
user confirms first. When unlocking still fails right after a start over, the
provider likely refuses its PRF output, so stop offering to forget a stored
secret, but keep offering `onlyPending`, which forgets only the pending key,
loses nothing, and lets the user try another provider. Where the platform
keeps nothing, it fails with [WrappedDeviceSecretNotPersistedError](https://evolu.dev/docs/api-reference/common/local-first/Auth/interfaces/WrappedDeviceSecretNotPersistedError)
before anything else.