Every sensitive request on DFNS is signed. Creating a wallet, submitting a transaction, changing a policy. The caller proves, with a private key it alone holds, that it intended this exact action, and the platform verifies that proof before anything happens. We call this user action signing, and it is the foundation of every control on the platform. Since the first version, it has needed three HTTP requests to complete. Today we are introducing the second version, which needs only one.
Authentication 2.0 keeps the protocol’s guarantees and removes two network round trips from every protected operation. For an application that creates wallets or submits transactions at volume, that is about 67% fewer HTTP requests per operation, with no change to what is verified. It is available now as an opt-in preview for server-side integrations that sign with a Key credential.
What changes
The first version of user action signing works in three steps. The application asks DFNS for a challenge. It signs that challenge and exchanges the signature for a user action token. Then it submits the operation with that token. Each step is a round trip to the API.
User action signing 1.0Application → DFNS: request a challenge POST /auth/action/initApplication: sign the challengeApplication → DFNS: obtain an action token POST /auth/actionApplication → DFNS: submit the operation POST /wallets
User action signing 2.0Application: build and sign a request-specific challengeApplication → DFNS: submit the operation POST /wallets with the signed assertionIn the second version, the application builds the challenge itself. The challenge describes the exact action the application is about to perform, carries a timestamp and a fresh random value, and is signed with the private key of an eligible credential. The signed assertion travels in the same request as the operation, and DFNS verifies it as part of processing that request. Nothing is asked of the API before the operation is submitted.
What stays the same
This is the part a security reviewer should read first, together with the section on replay protection below. The second version changes who builds the challenge and how its freshness is enforced. It does not change the checks on the caller, the credential, the action or the authorization.
A valid bearer token is still required. DFNS checks that the token is eligible for the route and resolves the signing credential from the credentials available to that authenticated caller. The credential must be an active Key credential. For a personal access token, that means the key associated with the token.
The signature still binds the exact action. It covers the hostname, the HTTP method, the path and a digest of the request body, and it now covers the query string too, which the first version’s challenge never included. DFNS compares every one of them with the incoming request. Change the destination, the endpoint, a query parameter or a single byte of the payload, and verification fails. The signed organization or tenant identifier must also match the scope the bearer token authenticated.
Permissions still apply. The Policy Engine still runs. In hybrid and on-premises deployments, the Governance Engine still re-validates the signature before the signers participate. A valid signature proves that the caller holds the key. The caller must also be authorized to perform the action, and that is checked exactly as before. Every verified action receives a user action ID and recorded credential evidence, so the audit trail is the same.
The first version remains fully supported, and it remains the flow for passkey-based integrations, where a person authorizes an action on a device. The second version is for applications that can sign with a Key credential, which in practice means backend services using service accounts or personal access tokens.
How it works
For a wallet creation request, the application first serializes its JSON body. It then builds a challenge with the following fields.
| Field | What the application supplies |
|---|---|
timestamp | The current Unix time in milliseconds, as a 13-digit string |
nonce | A fresh 16-byte cryptographically random value, encoded as unpadded base64url |
host | The canonical API hostname, lowercase and without a port |
method | The uppercase HTTP method, such as POST |
path | The canonical API path, including any query string in its original order and encoding |
payloadHash | The unpadded base64url SHA-256 digest of the UTF-8 JSON body |
orgId or tenantId | Exactly one scope identifier, matching the scope authenticated by the bearer token |
The application base64url-encodes the challenge JSON and places it inside a client data object.
{ "type": "key.get", "challenge": "<base64url-encoded challenge JSON>"}It signs the serialized UTF-8 bytes of that object with its private key and packages the result as a Key assertion.
{ "kind": "Key", "credentialAssertion": { "credId": "<registered credential ID>", "clientData": "<base64url-encoded client-data JSON>", "signature": "<base64url-encoded signature>", "algorithm": "SHA256" }}The algorithm field is optional. When it is absent, DFNS determines the digest from the registered key, which is what the DFNS SDK key signers do, so the simplest choice is to leave it out for any key type. The example sets it to SHA256 explicitly; the documented values are RSA-SHA256, SHA256 and SHA512. Ed25519 keys sign the raw client data with no separate digest, so there is nothing to specify for them. For an ECDSA key, the signature must be DER encoded. That is what OpenSSL, Node’s crypto.sign and AWS KMS produce by default. WebCrypto returns the raw r||s concatenation instead, as do some HSM and KMS APIs; convert it to DER before base64url-encoding it, or verification fails. All base64url values are unpadded.
Finally, the application base64url-encodes the assertion and sends it over HTTPS in the x-dfns-useraction header, alongside its bearer token and the body it hashed.
POST /wallets HTTP/1.1Host: api.dfns.ioAuthorization: Bearer <access-token>Content-Type: application/jsonx-dfns-useraction: <base64url-encoded Key assertion JSON>
{"network":"EthereumSepolia"}A few details matter in practice. Serialize the body once and use that same string for hashing and for sending, because reformatting JSON after signing can change the digest even when the parsed content looks identical. For a request without a body, hash the empty string. With transport compression, hash the UTF-8 JSON before it is compressed. Deployments with path-based routing must sign the canonical authorization hostname and API path configured for that deployment.
Replay protection
A signed request must not be usable twice, and must not be usable late. Two mechanisms cover this.
The timestamp limits the window. DFNS accepts a request only if its timestamp is at most 25 seconds in the past or 10 seconds in the future, so clocks need to be reasonably synchronized.
The nonce makes each request unique. After verifying the signature and the request binding, DFNS records the nonce together with the identity of the signed request in shared persistent storage. Any reuse of that combination is rejected, including when the replay reaches a different API replica. Replay records are kept beyond the entire timestamp window. If DFNS cannot record the nonce, the request fails rather than proceeding. And because signature verification happens first, an invalid signature can never consume the nonce of a valid request.
What it saves
The benefit is the removal of two sequential round trips. If the network round-trip time between an application and DFNS is R, the first version spends about 3R waiting on the network for each protected operation. The second version spends about R.
| Illustrative round-trip time | 1.0, network waiting | 2.0, network waiting | Saved |
|---|---|---|---|
| 20 ms | 60 ms | 20 ms | 40 ms |
| 50 ms | 150 ms | 50 ms | 100 ms |
| 100 ms | 300 ms | 100 ms | 200 ms |
These are network-only figures, not production benchmarks. End-to-end latency also includes client signing, server verification, authorization and the operation itself, so the measured improvement depends on the workload and the network. The reduction in HTTP requests is fixed. The second version also removes the two preliminary API calls, and with them the challenge and token bookkeeping they required. Signature verification itself now happens inside the operation request. Blockchain confirmation times are unchanged, since they belong to the network, not to DFNS.
Adopting it
Authentication 2.0 is an opt-in preview, disabled by default and enabled per tenant. To adopt it, ask DFNS to enable the preview for your tenant and use an eligible Key credential with the appropriate bearer token. With the TypeScript SDK, version 0.8.32 or later, pass useClientChallenge: true to AsymmetricKeySigner or AwsKmsKeySigner and the SDK builds and signs the challenge for you. Keep the private signing key protected in your backend environment, synchronize your clocks, and build a fresh challenge for every request.
One operational habit carries over rather than changes. The first version’s action token was already single-use, and in the second version the same signed request cannot be re-sent once its 25-second timestamp window has closed. If a request times out, check whether the operation completed before re-submitting it. Replay protection prevents a signed request from being reused, so a newly signed request is a new attempt at the operation, not a retry of the old one.
Where it goes
The second version of user action signing will become the default for Key credential integrations once the preview period ends. The TypeScript SDK already supports it behind an opt-in flag, and the other SDKs will follow, so most applications adopt it by upgrading rather than by rewriting. The passkey flow stays as it is, because a person authorizing an action on a device is a different problem from a service signing at volume, and it deserves its own protocol.
The principle behind both is the same one that has guided user action signing from the first version. The platform never trusts the host that sends a request. It trusts a signature over the exact action, from a key the caller alone holds, checked against who that caller is and what they are allowed to do. Authentication 2.0 keeps all of that and asks the network for less.
Get started
- Learn more about onchain core banking: dfns.co
- Explore the platform and documentation: docs.dfns.co
- Start building today on DFNS: app.dfns.io/get-started