WalletHandlers
The WalletHandlers class is a server-side component designed to integrate DID Connect into web frameworks like Express.js. It automates the creation of the required API endpoints, simplifying the backend implementation of a DID Connect session. This class works in tandem with the WalletAuthenticator, which handles the cryptographic signing and verification of messages.
WalletHandlers extends Node.js's EventEmitter, allowing you to listen for key events during the authentication lifecycle.
How it Works#
The WalletHandlers class sets up a series of HTTP endpoints for a given action (e.g., 'login'). These endpoints manage the entire DID Connect flow, from generating a session token to receiving the final wallet response.
Constructor#
new WalletHandlers(config)
Creates an instance of the DID Connect handlers. This instance can then be used to attach multiple workflows (actions) to your application.
Parameter | Type | Description |
|---|---|---|
|
| The configuration object for the handler instance. |
|
| Required. An object responsible for creating, reading, and destroying session tokens. You can use |
|
| Required. An instance of |
|
| Optional. A function to transform the URL path included in the DID Connect deep link. Defaults to an identity function. |
|
| Optional. A global function called just before a claims request is sent to the wallet. It can be used for session validation or permission checks. Throwing an error will halt the process. |
|
| Optional. An object for customizing endpoint behavior and parameters. |
|
| The URL prefix for all created endpoints. Defaults to |
|
| The delay in milliseconds before a completed session is cleaned up from storage. Defaults to |
|
| The query parameter key for the session token. Defaults to |
|
| The query parameter key for the encryption key. Defaults to |
|
| The query parameter key for the protocol version. Defaults to |
Example#
const { WalletAuthenticator, WalletHandlers } = require('@did-connect/server');
const { fromRandom } = require('@ocap/wallet');
const TokenStorage = require('@did-connect/storage-memory');
// 1. Configure the authenticator with your app's wallet and info
const appWallet = fromRandom();
const authenticator = new WalletAuthenticator({
wallet: appWallet,
appInfo: {
name: 'My App',
description: 'My App Description',
icon: 'https://example.com/icon.png',
link: 'https://example.com',
},
});
// 2. Create a handler instance
const tokenStorage = new TokenStorage();
const handlers = new WalletHandlers({
authenticator,
tokenStorage,
});
// Now, the 'handlers' instance is ready to have actions attached to it.Methods#
attach(config)#
The .attach() method is the primary way to configure and enable a specific DID Connect workflow, known as an "action," on your web server. It registers a set of standard routes with your Express app instance.
For an action named {action}, the following routes are created under the configured prefix:
GET /{prefix}/{action}/token: Creates a new session token and a deep link URL for the QR code.POST /{prefix}/{action}/token: Also creates a new session token.GET /{prefix}/{action}/status: Allows the web client to poll for the session status.GET /{prefix}/{action}/timeout: Allows the web client to manually expire a token.GET /{prefix}/{action}/auth: Endpoint for the wallet to fetch the claims request.POST /{prefix}/{action}/auth: Endpoint for the wallet to submit the signed response.GET /{prefix}/{action}/auth/submit: Alternative submission endpoint for web wallets.
Parameter | Type | Description |
|---|---|---|
|
| Required. The Express application instance to attach the routes to. |
|
| Required. A unique name for this workflow (e.g., 'login', 'payment'). This becomes part of the URL path. |
|
| An array of claim objects that your application is requesting from the user. For example, |
|
| Required. A callback that executes when the user successfully authenticates in their wallet and the signed response is verified. This is where you handle the received claims. Signature: |
|
| Optional. A callback that runs when a new session token is generated. |
|
| Optional. A callback that runs when the wallet scans the QR code and requests the claims. This overrides the global |
|
| Optional. A callback for when the user explicitly declines the request in their wallet. Signature: |
|
| Optional. A callback that runs after the entire process is finished for a session (either success or decline). Useful for final cleanup. |
|
| Optional. A callback for when the session token expires before completion. |
|
| Optional. A callback for handling any errors that occur during the process. Defaults to |
|
| Optional. Specifies if an |
|
| Optional. If set to |
Example#
This example sets up a simple login flow on an Express server.
const express = require('express');
const { WalletAuthenticator, WalletHandlers } = require('@did-connect/server');
const { fromRandom } = require('@ocap/wallet');
const TokenStorage = require('@did-connect/storage-memory');
const app = express();
const port = 3000;
// 1. Configure Authenticator
const appWallet = fromRandom();
const authenticator = new WalletAuthenticator({
wallet: appWallet,
appInfo: {
name: 'My Express App',
description: 'A simple DID Connect login example',
icon: 'https://arcblock.oss-cn-beijing.aliyuncs.com/images/wallet-round.png',
link: `http://localhost:${port}`
},
});
// 2. Configure Handlers
const tokenStorage = new TokenStorage();
const handlers = new WalletHandlers({
authenticator,
tokenStorage,
});
// 3. Attach a "login" action to the Express app
handlers.attach({
app,
action: 'login',
claims: [
{ type: 'profile', fields: ['fullName', 'email', 'avatar'] }
],
onAuth: (req, res, claims) => {
// This function is called upon successful authentication
const userProfile = claims.find(x => x.type === 'profile').fields;
console.log('User authenticated:', userProfile.fullName);
// Here, you would typically create a user session or JWT for your app
// The session status in tokenStorage is automatically updated to 'succeed'.
},
onDecline: (req, res, token) => {
console.log(`User declined session: ${token}`);
},
});
app.listen(port, () => {
console.log(`Example app listening on port ${port}`);
console.log(`To start a login session, open: http://localhost:${port}/api/did/login/token`);
});Events#
Since WalletHandlers is an event emitter, you can listen for changes in the session store.
created: Emitted when a new session token is created.(data)updated: Emitted when a session's state changes (e.g., from 'created' to 'scanned', then to 'succeed').(payload)deleted: Emitted when a session token is destroyed after completion or expiration.({ token })
Example#
handlers.on('updated', (payload) => {
console.log('Session updated:', payload.token, 'Status:', payload.status);
// You could use this event to push updates to the client via WebSockets.
});With WalletHandlers, you can quickly set up a robust backend for any DID Connect workflow. For more complete, end-to-end implementations, please see the Simple Login example.