Chain Multiple Workflows
DID Connect allows you to chain multiple interactions into a single, continuous user experience. Instead of a single request that asks for everything at once, you can create multi-step workflows, such as logging a user in, then asking for a verifiable credential, and finally requesting a signature for a transaction. This is accomplished using the nextWorkflow property in the onAuth callback.
This guide will walk you through creating a multi-step workflow where data is passed from one step to the next.
The Concept: nextWorkflow#
The core of this feature lies in the onAuth callback. When a user successfully completes a step in your workflow, your onAuth function can return an object containing a nextWorkflow URL and a nextToken. The DID Connect library then instructs the wallet to automatically proceed to this new URL, starting the next step in the chain without requiring the user to scan another QR code.
The library manages the session state, ensuring that the initial session is only marked as fully complete after the final workflow in the chain has succeeded.
Here is a diagram illustrating the sequence of events in a two-step workflow:
Passing Data Between Steps#
To make workflows powerful, you often need to pass context from one step to the next. You can achieve this using nextWorkflowData and previousWorkflowData.
- Sending Data (
nextWorkflowData): In youronAuthcallback, return a JSON object in thenextWorkflowDataproperty. This object will be passed to the next step. - Receiving Data (
previousWorkflowData): The library automatically encodes this data and appends it to thenextWorkflowURL. In the subsequent step, this data becomes available in theonStartoronAuthcallback viaextraParams.previousWorkflowData.
The library intelligently merges data at each step. If workflow A passes { a: 'a' } to workflow B, and workflow B passes { b: 'b' }, the data received by workflow C will be a merged object: { a: 'a', b: 'b' }.
Example: Two-Step User Onboarding#
Let's implement a two-step process: first, the user connects their wallet (login), and second, we ask for their profile information. We'll also pass the user's DID from the first step to the second.
Step 1: Set up the DID Connect Handler#
We'll define a single action endpoint /api/did/onboarding that handles both steps. The logic inside the onAuth callback will determine what to do next.
// lib/index.js
const { WalletHandlers, WalletAuthenticator } = require('@did-connect/server');
const MemoryAuthStorage = require('@did-connect/storage-memory');
const axios = require('axios');
// ... (setup for express app, wallet, etc.)
const authenticator = new WalletAuthenticator({ wallet, appInfo });
const tokenStorage = new MemoryAuthStorage();
const handlers = new WalletHandlers({ authenticator, tokenStorage });
let serverUrl = ''; // Will be set when server starts
handlers.attach({
app,
action: 'onboarding',
claims: {
profile: () => ({
fields: ['fullName', 'email'],
description: 'Please provide your profile information for onboarding.',
}),
},
onAuth: async ({ userDid, claims, step, extraParams }) => {
// Step 0 is the initial authPrincipal (login) claim
if (step === 0) {
console.log(`User ${userDid} just logged in. Proceeding to profile step.`);
// Generate the token for the next step in the workflow
const { data } = await axios.get(`${serverUrl}/api/did/onboarding/token`);
return {
nextWorkflow: data.url,
nextToken: data.token,
nextWorkflowData: {
didFromLogin: userDid
},
};
}
// Step 1 is the profile claim
if (step === 1) {
const profile = claims.find(x => x.type === 'profile');
console.log('Onboarding complete for user:', userDid);
console.log('Received profile:', profile);
console.log('DID from login step:', extraParams.previousWorkflowData.didFromLogin);
return { successMessage: 'Onboarding complete!' };
}
// Fallback for any other steps
return {};
},
onStart: ({ extraParams }) => {
if (extraParams.previousWorkflowData) {
console.log('Starting next step with data from previous workflow:');
console.log(extraParams.previousWorkflowData);
}
}
});
// ... (start express server and set serverUrl)
// server.listen(port, () => { serverUrl = `http://localhost:${port}`; });Step 2: Understand the Flow#
- Initiation: Your application generates a DID Connect QR code for the
/api/did/onboarding/tokenendpoint. - User Login: The user scans the code. The default
authPrincipalclaim is processed. TheonAuthcallback is triggered forstep: 0. - Chaining Logic: Inside
onAuthforstep: 0, we detect it's the first step. We programmatically call our own/tokenendpoint again to get a new URL (nextWorkflow) and session ID (nextToken) for the next step. We also includenextWorkflowData. - Wallet Redirect: The wallet receives the
nextWorkflowand automatically navigates to it. This triggers the second part of the flow: theprofileclaim. - Data Received: The
onStartcallback for the second step logs thepreviousWorkflowDatathat was passed from the first step. - User Provides Profile: The user approves the request for their profile information.
- Completion: The
onAuthcallback is triggered forstep: 1. Since we don't return anothernextWorkflow, the chain ends. The library marks both the initial session and the second session assucceed.
By returning nextWorkflow and nextToken from the onAuth callback, you can create sophisticated, user-friendly, multi-step interactions without friction.
To explore more advanced patterns, see how to implement Delegated Connect or use Dynamic Claims.