Start building: login & create a wallet via API
Building your own app? Learn how to consume the Dfns APIs using your Employee login.
There are many ways to authenticate to the Dfns API, you can find all the details on Authentication. In this example we will simply follow the normal login flow.
This flow requires using your credentials to sign challenges. For convenience we will use a web front-end to do this.
Setup the environment
For this example, we'll use a basic React app. Let's first prepare the environment: run
npx create-react-app dfns-direct-login
and answer the few questions in order to get set. You can then run the server with
npm start
Then copy-paste the base page for starter:
import "./App.css";
import { useRef, useState } from "react";
const baseURL = "https://api.dfns.io"; // .io for prod, .ninja for staging
const credentialName = "DemoLogin";
const DEFAULT_ORG_ID = "or-*****-*****-****************"; // your org id here for convenience
const DEFAULT_EMAIL = "*******@mycompany.com"; // your email here for convenience
function App() {
const codeRef = useRef();
const [registrationState, setRegistrationState] = useState({});
const handleRegister = () => {
const code = codeRef.current.value;
/* HERE GOES THE CODE FOR REGISTERING A CREDENTIAL */
// uncomment when completed
// .then(setRegistrationState)
// .catch(setRegistrationState)
};
const orgIdRef = useRef(DEFAULT_ORG_ID);
const emailRef = useRef(DEFAULT_EMAIL);
const [loginState, setLoginState] = useState({});
const handleLogin = () => {
const orgId = orgIdRef.current.value;
const email = emailRef.current.value;
/* HERE GOES THE CODE FOR LOGIN */
// uncomment when completed
// .then(setLoginState)
// .catch(setLoginState)
};
return (
<div className="App">
<h1>User login demo app</h1>
<h2>1. Credential registration</h2>
<div>
<p>If not done already, you need to register on this website. Get a Credential Code from the <a rel="noreferrer" href="https://app.dfns.io/v3/settings/authentication/credentials" target="_blank">dashboard</a> (Settings > Authentication > Credentials)</p>
<p><label>Enter your Credential Code: <input type="text" ref={codeRef}></input></label></p>
<p><button onClick={handleRegister}>Register</button></p>
<p>Current state: <small><code>{JSON.stringify(registrationState, null, 2)}</code></small></p>
</div>
<h2>2. User login</h2>
<div>
<p><label>Enter your Organization Id Code: <input type="text" ref={orgIdRef} defaultValue={DEFAULT_ORG_ID}></input></label</p>
<p><label>Enter your Email address: <input type="email" ref={emailRef} defaultValue={DEFAULT_EMAIL}></input></label></p>
<p><button onClick={handleLogin}>Login</button></p>
<p>Current state: <small><code>{JSON.stringify(loginState, null, 2)}</code></small></p>
</div>
</div>
);
}
export default App;
/* Helpers to convert challenges from Dfns format to WebAuthn format */
function base64url_decode(value) {
const m = value.length % 4;
return Uint8Array.from(
atob(
value
.replace(/-/g, "+")
.replace(/_/g, "/")
.padEnd(value.length + (m === 0 ? 0 : 4 - m), "=")
),
(c) => c.charCodeAt(0)
).buffer;
}
function base64url_encode(buffer) {
return btoa(
Array.from(new Uint8Array(buffer), (b) => String.fromCharCode(b)).join("")
)
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=+$/, "");
}
function string_to_ArrayBuffer(value) {
var enc = new TextEncoder();
return enc.encode(value).buffer;
}
Registering a new credential
WebAuthn credentials, that Dfns uses for logging you in and signing all your requests, are platform-specific. In particular, you cannot reuse the dashboard credentials for another front-end. In this case we are using localhost as a front-end, so we need to create a dedicated credentials for it.
There is an easy way to enable this use case from the dashboard: head to your credentials and click "Get Cred. Code". This code will then be usage to register a new credentials from a new front-end. That could be a new device, but in our case it's just a new URL. This code is valid for 60 seconds only so you will have to request it only when you are ready.
We have already prepared the function for you to fill-in:
const handleRegister = () => {
const code = codeRef.current.value;
/* HERE GOES THE CODE FOR REGISTERING A CREDENTIAL */
// uncomment when completed
// .then(setRegistrationState)
// .catch(setRegistrationState)
};
As explained in Create Credential With Code flow, in order to register the credentials we need to
Get a challenge code using
POST /auth/credentials/code/init
fetch(`${baseURL}/auth/credentials/code/init`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
code: registrationCode,
credentialKind: "Fido2",
}),
}).then((response) => response.json())
Create a credentials from our browser based on the challenge we received. This steps requires some adaptation as Dfns send Base64URL-encoded strings when the browsers expect ArrayBuffers, hence the length of this code:
.then((dfnsChallenge) => {
return navigator.credentials.create({
publicKey: {
challenge: string_to_ArrayBuffer(dfnsChallenge.challenge),
pubKeyCredParams: dfnsChallenge.pubKeyCredParams,
rp: { name: credentialName },
user: {
displayName: dfnsChallenge.user.displayName,
id: string_to_ArrayBuffer(dfnsChallenge.user.id),
name: dfnsChallenge.user.name,
},
attestation: dfnsChallenge.attestation,
excludeCredentials: dfnsChallenge.excludeCredentials.map(({ id, type }) => ({
id: base64url_decode(id),
type,
})),
authenticatorSelection: dfnsChallenge.authenticatorSelection,
}
}).then((browserCredential) => {
return {
challengeIdentifier: dfnsChallenge.challengeIdentifier,
credentialName: credentialName,
credentialKind: dfnsChallenge.kind,
credentialInfo: {
credId: browserCredential.id,
attestationData: base64url_encode(browserCredential.response.attestationObject),
clientData: base64url_encode(browserCredential.response.clientDataJSON),
},
};
});
})
Register those new credentials using
POST /auth/credentials/code/verify
.then((signedChallenge) => fetch(`${baseURL}/auth/credentials/code/verify`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(signedChallenge)
}).then((response) => response.json())
Run this code and your browser will ask you to create a new passkey for your localhost website. You only need to do this once, as later your browser will offer to use the 🔑 passkey automatically.
Now you can also check on the dashboard in Settings > Credentials, your new credential (default name is "DemoLogin") has been registered!
Login using your 🔑 Passkey
In a very similar way, as explained in Login, you need to request a Bearer token for authenticating all your API requests.
The placeholder is there for you already:
const handleLogin = () => {
const orgId = orgIdRef.current.value;
const email = emailRef.current.value;
/* HERE GOES THE CODE FOR LOGIN */
// uncomment when completed
// .then(setLoginState)
// .catch(setLoginState)
};
For this we need to:
Get a "User Login Challenge" using
POST /auth/login/init
fetch(`${baseURL}/auth/login/init`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
orgId: orgId,
username: email,
})
}).then((response) => response.json())
Get the browser to 🔑 sign the challenge to prove identity. This steps requires some adaptation as Dfns send Base64URL-encoded strings when the browsers expect ArrayBuffers, hence the length of this code. 💡 We recommend creating a function for this, as you will need to reuse it for all your API requests later.
.then(signChallenge)
function signChallenge(dfnsChallenge) {
return navigator.credentials.get({
publicKey: {
challenge: string_to_ArrayBuffer(dfnsChallenge.challenge),
allowCredentials: dfnsChallenge.allowCredentials.webauthn.map(({ id, type }) => ({
id: base64url_decode(id),
type,
})),
userVerification: dfnsChallenge.userVerification
}
})
.then((browserCredential) => {
console.log(browserCredential);
return {
challengeIdentifier: dfnsChallenge.challengeIdentifier,
firstFactor: {
kind: "Fido2",
credentialAssertion: {
credId: browserCredential.id,
clientData: base64url_encode(browserCredential.response.clientDataJSON),
authenticatorData: base64url_encode(browserCredential.response.authenticatorData),
signature: base64url_encode(browserCredential.response.signature),
userHandle: base64url_encode(browserCredential.response.userHandle),
},
},
};
});
}
Verify the signature and get a Bearer token using
POST /auth/login
.then((signedChallenge) => fetch(`${baseURL}/auth/login`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(signedChallenge),
}).then((response) => response.json())
Run this code and your browser will ask you to sign in with the passkey created earlier for your localhost website. You then get a token! Hooray!
Now you can use the Bearer token in a Authorization
header in all your requests.
First API request: read the wallet list
Use your Bearer token to list the wallets:
fetch(`${baseURL}/wallets`, {
method: "GET",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`
}
}).then((response) => response.json());
Congratulations! you are already able to query data from your account after login in!
User Actions: signing requests
For any action that's modifying things in your account (e.g.: creating a wallet), Dfns asks for a signature, which is done by using your 🔑 passkey as earlier with the login.
As before this process requires multiple steps:
Get a "User Action Signature Challenge" from
POST /auth/action/init
. See details on Create User Action Signature Challenge.
const userActionPayload = { network: "Ethereum" };
const userActionHttpMethod = "POST";
const userActionHttpPath = "/wallets";
fetch(`${baseURL}/auth/action/init`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({
userActionPayload: JSON.stringify(userActionPayload),
userActionHttpMethod,
userActionHttpPath,
}),
}).then((response) => response.json())
Sign the challenge
For this we can reuse the previously-created function:
.then(signChallenge)
Return to the Dfns system and request a "User Action Token" using
POST /auth/action
and get the the token!
.then((signedChallenge) => fetch(`${baseURL}/auth/action`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify(signedChallenge),
}))
.then((response) => response.json())
.then((response) => response.userAction)
Awesome! you got a token! note that it is only usable once so you will have to request a new one (and sign that request) every time you need to do an action on your account.
Include the new token on top of your login token in your requests
.then((userAction) => fetch(`${baseURL}${userActionHttpPath}`, {
method: userActionHttpMethod,
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
"X-DFNS-USERACTION": userAction,
},
body: JSON.stringify(userActionPayload)
}))
.then((response) => response.json());
Congratulations! You can now make server-side API calls using your service account. Now start building your app using our Typescript SDK and specifically the Service Account sample app.
That was a key step into integrating our platform. You can head back to the previous page:
Onboarding to DfnsLast updated