Skip to main content
You can find more details in the developers guides section.

Fido2 signing flow

This flow is for a human user interacting with a frontend (website, mobile app, etc) and signing the request using his passkey from that frontend. The authToken to use here is from that user, refer to the login flows for details how to obtain that token.
1

Prepare your endpoint call payload

For starting a signing session you need to prepare the API call you intend to make eventually.As an example let’s use Transfer Asset.
  • Method: POST
  • Path: /wallets/wa-12345-12345-12345678910/transfers
  • Payload: {"kind": "Native", "to": "0xe5a2ebc128e262ab1e3bd02bffbe16911adfbffb", "amount": "100000"}
import { DfnsDelegatedApiClient } from '@dfns/sdk'

const dfnsClient = new DfnsDelegatedApiClient({
  baseUrl: 'https://api.dfns.io', // base Url of DFNS API
  orgId: 'org-2ng9jv-80cfc-983pop0iauf2sv8r', // ID of the DFNS Organisation
  authToken: userAuthToken, // Auth token of the User
})

const payload = {
    kind: "Native",
    to: "0xe5a2ebc128e262ab1e3bd02bffbe16911adfbffb",
    amount: "100000"
  }
from dfns_sdk import DfnsDelegatedClient, DfnsDelegatedClientConfig

config = DfnsDelegatedClientConfig(
    auth_token=user_auth_token,  # Auth token of the User
    base_url="https://api.dfns.io",  # base Url of DFNS API
)

dfns_client = DfnsDelegatedClient(config)

payload = {
    "kind": "Native",
    "to": "0xe5a2ebc128e262ab1e3bd02bffbe16911adfbffb",
    "amount": "100000"
}
const payload = {
    kind: "Native",
    to: "0xe5a2ebc128e262ab1e3bd02bffbe16911adfbffb",
    amount: "100000"
  }
2

Request a User Action Challenge

Use : Create User Action Challenge.This starts a user action signing session, returning a challenge that will be used to verify the user’s intent to perform an action. You need to provide details about the intended call:
const challenge = await dfnsClient.wallets.transferAssetInit(payload)
wallet_id = "wa-12345-12345-12345678910"
challenge = dfns_client.wallets.transfer_asset_init(wallet_id, body=payload)
const url = 'https://api.dfns.io/auth/action/init';
const options = {
  method: 'POST',
  headers: {
    Authorization: 'Bearer <token>',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify(payload)
};

try {
  const response = await fetch(url, options);
  const data = await response.json();
} catch (error) {
  console.error(error);
}

3

Get the user to sign the challenge

You need to forward the challenge and associated information to the frontend (browser or app) to handle the signing operation using WebAuthn APIs.
import { WebAuthnSigner } from '@dfns/sdk-browser'
const webauthn = new WebAuthnSigner({ relyingParty: { id: 'acme.com', name: 'Acme' } })

const challenge = "Object coming from the backend"

const signedChallenge = await webauthn.sign(challenge) // this call will trigger the request for interacting with the passkey

// Forward signedChallenge back to the backend
import { fromBase64Url, toBase64Url } from '@dfns/sdk/utils' // please refer to the code if you want to copy those functions
import { Buffer } from 'buffer'

const challenge = "Object coming from the backend"

const response = await navigator.credentials.get({
      publicKey: {
        challenge: Buffer.from(challenge.challenge),
        allowCredentials: challenge.allowCredentials.webauthn.map(({ id, type }) => ({
          id: fromBase64Url(id).buffer as ArrayBuffer,
          type,
        })),
        rpId: this.conf.relyingParty.id,
        userVerification: challenge.userVerification,
        timeout: this.conf.timeout ?? DEFAULT_WAIT_TIMEOUT,
      },
    })

const signedChallenge = {
      kind: 'Fido2',
      credentialAssertion: {
        credId: credential.id,
        clientData: toBase64Url(Buffer.from(assertion.clientDataJSON)),
        authenticatorData: toBase64Url(Buffer.from(assertion.authenticatorData)),
        signature: toBase64Url(Buffer.from(assertion.signature)),
        userHandle: assertion.userHandle ? toBase64Url(Buffer.from(assertion.userHandle)) : undefined,
      },
    }

// Forward signedChallenge back to the backend
4

Request a User Action token

Use . Refer to Create User Action Signature for endpoint details.Completes the user action signing process and provides a signing token that can be used to verify the user intended to perform the action.
const signedChallenge = "object coming back from the frontend"
/*
Requesting a token is hidden into
the function used in the next step
*/
signed_challenge = "object coming back from the frontend"
# Requesting a token is hidden into
# the function used in the next step
const signedChallenge = "object coming back from the frontend"
const url = 'https://api.dfns.io/auth/action';
const options = {
  method: 'POST',
  headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
  body: JSON.stringify({
    challengeIdentifier:data["challengeIdentifier"], // data from the first step
    firstFactor: signedChallenge
  })
};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  const userActionToken = data["userAction"] // token to use in the next step
} catch (error) {
  console.error(error);
}
5

Use the User Action token

Add the User Action token to the X-DFNS-USERACTION header of the original API call that requires user action signing.
const transfer = await dfnsClient.wallets.transferAssetComplete(payload, signedChallenge)
wallet_id = "wa-12345-12345-12345678910"
transfer = dfns_client.wallets.transfer_asset_complete(
    wallet_id,
    body=payload,
    signed_challenge={
        "challengeIdentifier": challenge["challengeIdentifier"],
        "firstFactor": signed_challenge
    }
)
const url = `https://api.dfns.io/wallets/wa-12345-12345-12345678910/transfers`;
const options = {
  method: 'POST',
  headers: {
    Authorization: 'Bearer <token>',
    'X-DFNS-USERACTION': userActionToken,
    'Content-Type': 'application/json'
  },
  body: '{"kind":"Native","to":"0xe5a2ebc128e262ab1e3bd02bffbe16911adfbffb","amount":"1"}'
};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}

Asymmetric Keys signing flow

This flow is for machine-to-machine interactions: a backend signs the request using an asymmetric key, there is no signing request to the user. The backend can login as a Service Account (machine user with its own identity), as a user using their Personal Access Token. Just provide the right token as authToken for all the following API calls. You should also have registered the public key when you created the Service Account or the user’s Personal Access Token. The Private Key will be used to sign the challenges, make sure you use the private key linked to the authToken you are using!
1

Prepare your endpoint call payload

For starting a signing session you need to prepare the API call you intend to make eventually.As an example let’s use Transfer Asset.
  • Method: POST
  • Path: /wallets/wa-12345-12345-12345678910/transfers
  • Payload: {"kind": "Native", "to": "0xe5a2ebc128e262ab1e3bd02bffbe16911adfbffb", "amount": "100000"}
import { DfnsApiClient } from '@dfns/sdk'
import { AsymmetricKeySigner } from '@dfns/sdk-keysigner'

const signer = new AsymmetricKeySigner({
  credId: 'X2ktMzhxaTEtZTF1bTgtOXY1cG9yY2tkZDe1dG1jYg', // Credential ID
  privateKey: CREDENTIAL_PRIVATE_KEY, // Credential private key
})

const dfnsClient = new DfnsApiClient({
  baseUrl: 'https://api.dfns.io', // base Url of DFNS API
  orgId: 'or-2ng9jv-80cfc-983pop0iauf2sv8r', // ID of the DFNS Organisation
  authToken: '...', // Service Account or PAT auth token
  signer,
})

const payload = {
    kind: "Native",
    to: "0xe5a2ebc128e262ab1e3bd02bffbe16911adfbffb",
    amount: "100000"
  }
from dfns_sdk import DfnsClient, DfnsClientConfig, KeySigner

signer = KeySigner(
    credential_id="X2ktMzhxaTEtZTF1bTgtOXY1cG9yY2tkZDe1dG1jYg",  # Credential ID
    private_key=CREDENTIAL_PRIVATE_KEY,  # Credential private key
    app_origin="https://app.dfns.io"
)

config = DfnsClientConfig(
    auth_token="...",  # Service Account or PAT auth token
    base_url="https://api.dfns.io",  # base Url of DFNS API
    signer=signer
)

dfns_client = DfnsClient(config)

payload = {
    "kind": "Native",
    "to": "0xe5a2ebc128e262ab1e3bd02bffbe16911adfbffb",
    "amount": "100000"
}
const payload = {
    kind: "Native",
    to: "0xe5a2ebc128e262ab1e3bd02bffbe16911adfbffb",
    amount: "100000"
  }

const keyPrivateKey = "...private key..."
2

Request a User Action Challenge

Use : Create User Action Challenge.This starts a user action signing session, returning a challenge that will be used to verify the user’s intent to perform an action. You need to provide details about the intended call:
// This step is hidden when you use the SDK.
# This step is hidden when you use the SDK.
const url = 'https://api.dfns.io/auth/action/init';
const options = {
  method: 'POST',
  headers: {
    Authorization: 'Bearer <token>',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify(payload)
};

try {
  const response = await fetch(url, options);
  const data = await response.json();
} catch (error) {
  console.error(error);
}

3

Sign the challenge

Use a crypto library or a KMS to sign the challenge.
// This step is hidden when you use the SDK.
# This step is hidden when you use the SDK.
  // The data being signed includes information that is important for validating the request originated from a valid location.
  const clientData: Buffer = Buffer.from(
    JSON.stringify({
      type: 'key.get',
      challenge: challenge.challenge,
      origin: origin,
      crossOrigin: false,
    })
  )

  // Signing can be done locally or by calling an external signer (like AWS KMS).
  const signature = crypto.sign(
    undefined,
    clientData,
    keyPrivateKey
  )

  // Pass back the signature, and the data that was signed so both can be parsed and validated properly.
  const signedChallenge = {
    clientData: clientData.toString('base64url'),
    credId: challenge.allowCredentials.key[0].id,
    signature: signature.toString('base64url'),
  }
4

Request a User Action token

Use . Refer to Create User Action Signature for endpoint details.Completes the user action signing process and provides a signing token that can be used to verify the user intended to perform the action.
// This step is hidden when you use the SDK.
# This step is hidden when you use the SDK.
const signedChallenge = "object coming back from the frontend"
const url = 'https://api.dfns.io/auth/action';
const options = {
  method: 'POST',
  headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
  body: JSON.stringify({
    challengeIdentifier:data["challengeIdentifier"], // data from the first step
    firstFactor: signedChallenge
  })
};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  const userActionToken = data["userAction"] // token to use in the next step
} catch (error) {
  console.error(error);
}
5

Use the User Action token

Add the User Action token to the X-DFNS-USERACTION header of the original API call that requires user action signing.
const transfer = await dfnsClient.wallets.transferAsset(payload)
wallet_id = "wa-12345-12345-12345678910"
with dfns_client:
    transfer = dfns_client.wallets.transfer_asset(wallet_id, body=payload)
const url = `https://api.dfns.io/wallets/wa-12345-12345-12345678910/transfers`;
const options = {
  method: 'POST',
  headers: {
    Authorization: 'Bearer <token>',
    'X-DFNS-USERACTION': userActionToken,
    'Content-Type': 'application/json'
  },
  body: '{"kind":"Native","to":"0xe5a2ebc128e262ab1e3bd02bffbe16911adfbffb","amount":"1"}'
};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
Last modified on June 8, 2026