> ## Documentation Index
> Fetch the complete documentation index at: https://docs.dfns.co/llms.txt
> Use this file to discover all available pages before exploring further.

# Implement end-user recovery

> How to implement recovery flows for delegated wallet users so they can regain access if they lose their device or passkey credential.

When using [delegated wallets](/guides/developers/delegated-wallets), your end users control their own signing credentials. If they lose access to their device, they need a way to recover their wallet. This guide covers how to implement recovery flows.

<Note>
  Recovery credentials are encrypted private keys that Dfns stores as an opaque blob. The encryption happens in your frontend. Dfns never sees the decryption password. This means you own the full recovery experience: you decide the encryption scheme, the password format, and the recovery UX. The Dfns dashboard recovery flow at [app.dfns.io/recover](https://app.dfns.io/recover) only works for users who registered their recovery credential through the dashboard.
</Note>

## Recovery strategies

| Strategy                  | How it works                                   | Best for                    |
| ------------------------- | ---------------------------------------------- | --------------------------- |
| **Secondary credentials** | User registers credentials on multiple devices | Users with multiple devices |
| **Recovery credential**   | User stores a recovery password securely       | Self-service recovery       |

We recommend encouraging users to register credentials on multiple devices as the primary recovery method. Recovery credentials provide a fallback when that's not possible. Users can register multiple recovery credentials. One is typically created during initial registration, and more can be added later via the [Create Credential](/api-reference/auth/create-credential) flow.

<Tip>
  **Users should always have at least one recovery credential available.** Generate a recovery credential for your users during registration, and when a recovery credential is used (which invalidates all existing credentials), generate a new one as part of the recovery flow. This way users are never left without a recovery path.
</Tip>

<Tip>
  **Nudge users to register a second credential.** Consider prompting users to add a backup credential right after their initial registration, and showing a persistent security banner in your app until they do. End users who lose their only credential and have no recovery key will need you to initiate a [delegated recovery](/api-reference/auth/create-delegated-recovery-challenge) on their behalf. Proactive nudges reduce that support burden.
</Tip>

<Warning>
  You could store recovery credentials server-side and release them after identity verification (KYC, etc.), but this is not recommended. Whoever controls the decryption password can take control of the wallet, which undermines the delegated signing model.
</Warning>

## How recovery credentials work

A `RecoveryKey` credential uses an `encryptedPrivateKey` field - an opaque string that Dfns stores and returns to you. You implement the encryption, and the user keeps the decryption password.

<Note>
  Dfns stores the encrypted blob but never has access to the decryption password. Only the user can decrypt and use the recovery key.
</Note>

When used, a recovery credential triggers the recovery flow which **invalidates all existing credentials** for security.

## Implementing user-held recovery

<Note>
  If implementing recovery in a browser without Node.js, use the [@dfns/sdk-browser](/sdks/frontend/typescript/development#browserkeysigner) package for signing operations. If you must implement manually, see [Base64Url encoding](/guides/developers/generate-a-key-pair#base64-and-base64url-encoding) for correct encoding functions.
</Note>

<Steps>
  <Step title="Generate a recovery keypair during registration">
    When a user registers, generate a recovery keypair and encrypt the private key with a password. This must happen on the client side - the password should never be sent to your server.

    ```typescript title="Frontend - Recovery credential generation" theme={null}
    import crypto from 'crypto'

    // Generate a recovery keypair
    const { publicKey, privateKey } = crypto.generateKeyPairSync('ec', {
      namedCurve: 'prime256v1',
    })

    // Export keys
    const publicKeyPem = publicKey.export({ type: 'spki', format: 'pem' })
    const privateKeyPem = privateKey.export({ type: 'pkcs8', format: 'pem' })

    // Generate a random recovery password for the user
    const recoveryPassword = crypto.randomBytes(16).toString('base64') // Or use a word-based format

    // Derive an encryption key from the password
    const salt = crypto.randomBytes(16)
    const encryptionKey = crypto.pbkdf2Sync(recoveryPassword, salt, 100000, 32, 'sha256')

    // Encrypt the private key
    const iv = crypto.randomBytes(16)
    const cipher = crypto.createCipheriv('aes-256-gcm', encryptionKey, iv)

    let encrypted = cipher.update(privateKeyPem, 'utf8', 'base64')
    encrypted += cipher.final('base64')
    const authTag = cipher.getAuthTag()

    const encryptedPrivateKey = JSON.stringify({
      salt: salt.toString('base64'),
      iv: iv.toString('base64'),
      authTag: authTag.toString('base64'),
      data: encrypted,
    })
    ```
  </Step>

  <Step title="Display the recovery password to the user">
    Show the recovery password to the user with clear instructions:

    ```
    Your recovery password: Kx7mP2nQ9vBw3rYt

    Store this securely - you'll need it to recover your wallet if you lose access to your device.
    - Save it in a password manager
    - Write it down and store in a safe place
    - Do not share it with anyone
    ```

    The user must store this password themselves. You should not store it.
  </Step>

  <Step title="Register the recovery credential with Dfns">
    Include the recovery credential when registering the user. The public key and `encryptedPrivateKey` are sent to Dfns - the password stays with the user.

    You'll need to build the [Client Data](/api-reference/auth/credentials-data#key-password-protected-key-and-recovery-credential) and [Attestation Data](/api-reference/auth/credentials-data#key-password-protected-key-and-recovery-credential-2) objects as described in the credentials documentation.

    ```typescript title="Frontend - Include in registration request" theme={null}
    const recoveryCredential = {
      credentialKind: 'RecoveryKey',
      credentialInfo: {
        credId: generateCredentialId(),
        clientData: clientDataBase64,      // See credentials-data docs
        attestationData: attestationBase64, // Contains the public key - see credentials-data docs
      },
      encryptedPrivateKey: encryptedPrivateKey, // Encrypted blob only, password stays with user
    }
    ```
  </Step>

  <Step title="Implement the recovery flow">
    When a user needs to recover, all decryption happens on the client side:

    ```typescript title="Frontend - Recovery flow" theme={null}
    // 1. Prompt user for their recovery password
    const recoveryPassword = await promptUser('Enter your recovery password')

    // 2. Get the encrypted recovery key from Dfns (returned during recovery init)
    const encryptedData = JSON.parse(encryptedPrivateKey)

    // 3. Derive the encryption key from the user's password
    const encryptionKey = crypto.pbkdf2Sync(
      recoveryPassword,
      Buffer.from(encryptedData.salt, 'base64'),
      100000,
      32,
      'sha256'
    )

    // 4. Decrypt the private key
    const decipher = crypto.createDecipheriv(
      'aes-256-gcm',
      encryptionKey,
      Buffer.from(encryptedData.iv, 'base64')
    )
    decipher.setAuthTag(Buffer.from(encryptedData.authTag, 'base64'))

    let privateKeyPem = decipher.update(encryptedData.data, 'base64', 'utf8')
    privateKeyPem += decipher.final('utf8')

    // 5. Sign the recovery challenge with the decrypted key
    const recoveryKey = crypto.createPrivateKey(privateKeyPem)
    const newCredential = { /* user's new passkey */ }
    const signature = crypto.sign(
      undefined,
      Buffer.from(JSON.stringify(newCredential)),
      recoveryKey
    )

    // 6. Complete recovery - send signature to Dfns
    await dfnsClient.auth.recoverUser({
      body: {
        recovery: {
          credentialAssertion: {
            credId: recoveryCredentialId,
            clientData: clientDataBase64,  // See credentials-data docs
            signature: signature.toString('base64'),
          }
        },
        newCredential,
      }
    })
    ```
  </Step>

  <Step title="Generate new recovery credentials">
    After recovery, all previous credentials (including recovery credentials) are invalidated. Always generate a new recovery credential and display the new recovery password to the user. Do not let the user leave the recovery flow without a fresh recovery credential in place.
  </Step>
</Steps>

## Security considerations

* **Password strength** - Generate strong random passwords. Consider using word-based formats (like BIP39) for easier transcription.
* **Clear user instructions** - Users must understand the importance of storing their recovery password securely.
* **Rate limiting** - Prevent brute-force attempts on recovery flows.

## Related

<CardGroup>
  <Card title="Account Recovery" href="/api-reference/auth/account-recovery">
    Overview of recovery mechanisms for users.
  </Card>

  <Card title="Credentials Data" href="/api-reference/auth/credentials-data">
    How to build Client Data and Attestation Data objects.
  </Card>

  <Card title="Create Delegated Recovery Challenge" href="/api-reference/auth/create-delegated-recovery-challenge">
    API endpoint to initiate recovery for an end user.
  </Card>

  <Card title="Recover User" href="/api-reference/auth/recover-user">
    API endpoint to complete user recovery.
  </Card>
</CardGroup>
