Skip to main content
In order to login, or sign their Actions, users need to sign “challenges” using their Credentials. A Credential is essentially a public/private cryptographic keypair. The private key is held by the user, while the public key is provided to Dfns to register the credential for the user. The first time you registered on Dfns dashboard, you created a Passkey credential (see how here). You can also register additional credentials later on (see how here). Different kinds of Credentials can be created, depending on your use case, and how you prefer to manage them:
  • Fido2 Credentials (aka “Passkeys” / “WebAuthn”) -> Uses WebAuthn standard to create/manage passkeys on your device (see more about that below). You can use passkeys if you need a client-side User signature (eg. in a web app / native app).
  • Key Credentials -> “manually” generate keypairs yourself, and store them however you see fit (see How to generate a keypair). You can use Key Credential if you need a Service Account sitting in your server to also be the signer for example.
Depending on the Identity you are using, the Credentials supported are such:
IdentityWebAuthn CredentialsKey Credentials
User
PAT (Personal Access Token)🛑
Service Account🛑

User Credentials - Passkeys

Users can register with a WebAuthn Credential (aka “Passkey”) or with a raw Public/Private Key Passkeys is the common term used to describe the Fido2 standard called “WebAuthn”. It is a web authentication standard supported by most modern browsers, phones and devices, which leverages your devices key-management features (like touch ID on a mac, a phone authenticator, a yubikey, some password managers support creating and storing passkeys, etc). Those passkeys can then be used by the user to sign payloads when needed. Here’s some screenshots with some examples of WebAuthn prompts shown in your browser during Credential creation, or during Signing using those Credentials. Below is an example of the promps a user can see on a web app, when a challenge needs to be signed with the passkey: it’s asking the user for his biometrics (fingerprint) before using the passkey to sign.
You can read more about WebAuthn on webauthn.guide, and if you want you can test a WebAuthn demo on webauthn.io

Machine Credentials - Asymetric Keys

When registering a new long-lived access token (Service Account or Personal Access Token) to be used by a backend, you need to link a public-private key pair to your token. This is done by passing a public key with the API call. See Generate a Key Pair for information about how to generate a key pair.

Public Key Format

Public Keys need to be formated with PEM encoding.

PEM Encoding

PEM encoding base64 encodes the public key data and places it between a header and footer. The header and footer can be slightly different depending on the crypto library you use, but it will always have the same basic format:
-----BEGIN <OPTIONAL_VALUE> PUBLIC KEY-----
-----END <OPTIONAL_VALUE> PUBLIC KEY-----
For example
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEYsHwe62PxDXIXjvSd0/ZdndtvenLqZ6u62pp2/SejCs4NuJ5fOCsUDzqFXxBNJpA9CkFnqASaKP9n4N3XgQ1mQ==
-----END PUBLIC KEY-----

Converting from raw to PEM

Some cyrpto libraries do not support PEM format. If your library supports exporting to SPKI you can export the key into SPKI format, base64 encode that value, and place it inside the header and footer.
function arrayBufferToBase64(buffer) {
  const bytes = new Uint8Array(buffer)
  return btoa(String.fromCharCode(...bytes))
}

async function exportPublicKeyInPemFormat(key) {
  const exported = await crypto.subtle.exportKey('spki', key)
  const pem = `-----BEGIN PUBLIC KEY-----\n${arrayBufferToBase64(exported)}\n-----END PUBLIC KEY-----`
  return pem
}

Registering a key pair

When registering a user with a private key, you need to:
  1. Get a registration challenge from the Dfns API
  2. Create the key pair locally
  3. Sign the registration challenge and public key
  4. Return the signed challenge to the Dfns API

The Registration Challenge

A registration challenge is returned from calls to:
  • /auth/registration/init
  • /auth/registration/delegated
  • /auth/credentials/init
In all cases the challenge format is the same. You will recieve an object with the following properties (additional properties exist for managing credentials with WebAuthn):
fielddescription
challengeA string that will be signed with the new credential
temporaryAuthenticationTokenA JWT that is passed to the registration endpoint to identify the registration session
supportedCredentialKindsThe list of credential types that are supported, should always contain “Key”

How to Sign the Challenge with the Private Key

The user signs the challenge to prove they are in possession of the key being registered. The user will also sign the public key to ensure the key is not replaced when transmitted to Dfns.
Client Data
The user needs to format the challenge into a Client Data object.
Attestation Data
The client data object is then used to build the Attestation Data object.
Signing Example: First factor and Recovery credentials:
recoveryClientData = {
  type: 'key.get',
  challenge: base64url(JSON.stringify({
    "firstFactorCredential":{
      "credentialKind":"Key",
      "credentialInfo":{
        "credId":"6Ca6tAOFTx2odyJBnCoRO-gPvfpfy0EOoOcEaxfxIOk",
        "clientData":"eyJ0eXBlIjoia2V5LmNyZWF0ZSIsImNoYWxsZW5nZSI6Ik1XTTBNbVk1WVRRME1EUmlOemRoTlRGaE56WTVPRFF3TldJNVpUUTRZMlJoT0RaaU5EazNaVFl6T1RFNU9HWXlNRGN4WmpCall6azRNbVE1WXpZMU1BIiwib3JpZ2luIjoiaHR0cHM6Ly9hcHAuZGZucy5uaW5qYSIsImNyb3NzT3JpZ2luIjpmYWxzZX0",
        "attestationData":"eyJwdWJsaWNLZXkiOiAiLS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS1cbk1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRTljRzJtRTREV0hid3dsTFJTS0JMWjltNitRc0NcbmVPcVdKaDF4NVZ2UkhaTWFQTFFsUnJoaGdiSG04dW5hNGg4UytMNW84c1Y4SHZ1amJsM01yQVRqM1E9PVxuLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tXG4iLCJzaWduYXR1cmUiOiIzMDQ2MDIyMTAwOGUwMTA5ODQ4YzZmYzgzMDA0ZDBlNmM3ZmRhYzcxZGFlODUyNGZjNWEyOTA4MWQwMTJmODY1NDE2OTg2Y2UyOTAyMjEwMGY0N2UxYmVlNmM1MTc1YzQ0ODhiMTQzYzkzNmM2OGZhYzFhZTdlNzkzMWU3NmM2NzdkNDYzMzFlZDE0OWQxN2QifQ"
      }
    },
    "recoveryCredential":{
      "credentialKind":"RecoveryKey",
      "credentialInfo":{
        "credId":"GMkW0zlmcoMxI1OX0Z96LL_Mz7dgeu6vOH5_TOeGyNk",
        "clientData":"eyJ0eXBlIjoia2V5LmNyZWF0ZSIsImNoYWxsZW5nZSI6Ik1XTTBNbVk1WVRRME1EUmlOemRoTlRGaE56WTVPRFF3TldJNVpUUTRZMlJoT0RaaU5EazNaVFl6T1RFNU9HWXlNRGN4WmpCall6azRNbVE1WXpZMU1BIiwib3JpZ2luIjoiaHR0cHM6Ly9hcHAuZGZucy5uaW5qYSIsImNyb3NzT3JpZ2luIjpmYWxzZX0",
        "attestationData":"eyJwdWJsaWNLZXkiOiItLS0tLUJFR0lOIFBVQkxJQyBLRVktLS0tLVxuTUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFOWNHMm1FNERXSGJ3d2xMUlNLQkxaOW02K1FzQ1xuZU9xV0poMXg1VnZSSFpNYVBMUWxScmhoZ2JIbTh1bmE0aDhTK0w1bzhzVjhIdnVqYmwzTXJBVGozUT09XG4tLS0tLUVORCBQVUJMSUMgS0VZLS0tLS1cbiIsInNpZ25hdHVyZSI6IjMwNDYwMjIxMDBiZjBjZGU3ZGIyODQ0ZDhmOTIyZWQyOTNmN2E4NTVjM2U1Y2YzMjUxZjFhY2Q3M2I4MjNiNWZiOTIzZDNiY2FiMDIyMTAwY2YxM2U2ZDliY2ZiMjc3M2Q5ZDkyMDU4M2YwMWE0ODAyYmI4OTg5Y2NmZjMzNjJkYzJmN2U1ZjRmMTQzZjA2ZiJ9"
      },
      "encryptedPrivateKey":"LsXVskHYqqrKKxBC9KvqStLEmxak5Y7NaboDDlRSIW7evUJpQTT1AYvx0EsFskmriaVb3AjTCGEv7gqUKokml1USL7+dVmrUVhV+cNWtS5AorvRuZr1FMGVKFkW1pKJhFNH2e2O661UhpyXsRXzcmksA7ZN/V37ZK7ITue0gs6I="
    }
  })),
  origin: this.appOrigin,
  crossOrigin: false,
}

const clientData = Buffer.from(JSON.stringify(recoveryClientData))
const signature = crypto.sign(undefined, clientData, newKey.privateKey)