Delegated Connect
Delegated Connect allows one application, known as an 'agent', to perform DID Connect operations on behalf of another DID, the 'delegator'. This is a key feature for scenarios where a service needs to interact with users but wants to act under the authority of a more permanent or trusted identity, such as a company's primary DID, without exposing that primary DID's private key in the operational application.
This guide explains the concept and provides a step-by-step implementation of delegated authentication sessions.
How It Works#
The foundation of Delegated Connect is a verifiable proof of delegation. The delegator creates a signed JSON Web Token (JWT) that grants specific permissions to the agent. When the agent application initiates a DID Connect session, it presents this delegation JWT to the user's wallet. The wallet can then cryptographically verify that the agent is authorized to act on the delegator's behalf, ensuring a secure and transparent interaction.
The process can be visualized as follows:
Implementation#
Implementing Delegated Connect involves two main steps: creating the delegation proof and configuring the WalletAuthenticator
.
1. Create the Delegation Proof#
The delegation is a JWT signed by the delegator's secret key. This token specifies the agent's DID (agentDid
) and the permissions granted. The permissions define which claim types the agent is allowed to request.
const Jwt = require('@arcblock/jwt');
const { toDid } = require('@ocap/util');
const { fromRandom } = require('@ocap/wallet');
// The delegator (e.g., your company's main DID)
const delegator = fromRandom();
// The agent (your application's DID)
const agent = fromRandom();
const delegationToken = Jwt.signV2(delegator.address, delegator.secretKey, {
agentDid: toDid(agent.address),
permissions: [
{
role: 'DIDConnectAgent',
claims: [
'authPrincipal',
'profile',
'signature',
'prepareTx',
'agreement',
'verifiableCredential',
'asset',
'keyPair',
'encryptionKey',
],
},
],
// Set an expiration time for the delegation
exp: Math.floor(new Date().getTime() / 1000) + 3600, // Expires in 1 hour
});
console.log('Delegation Token:', delegationToken);
2. Configure the Authenticator#
When initializing WalletAuthenticator
, you must provide the agent's wallet
, along with the delegator
and the delegation
proof. These can be supplied as static objects or as functions for dynamic generation, which is useful for creating tokens just-in-time.
const { WalletAuthenticator } = require('@arcblock/did-connect');
const { fromRandom } = require('@ocap/wallet');
const Jwt = require('@arcblock/jwt');
const { toDid } = require('@ocap/util');
const agentWallet = fromRandom(); // Your application's wallet
const delegatorWallet = fromRandom(); // The principal/delegator's wallet
const authenticator = new WalletAuthenticator({
// The agent's wallet is used for signing the request
wallet: agentWallet,
appInfo: () => ({
name: 'My Awesome App (via Company Inc.)',
description: 'This app is acting on behalf of Company Inc.',
icon: 'https://arcblock.oss-cn-beijing.aliyuncs.com/images/wallet-round.png',
}),
chainInfo: () => ({
host: 'https://beta.abtnetwork.io/api',
id: 'beta',
}),
// Delegator configuration: who is granting permission
delegator: () => delegatorWallet,
// Delegation proof configuration: the proof of permission
delegation: () => {
return Jwt.signV2(delegatorWallet.address, delegatorWallet.secretKey, {
agentDid: toDid(agentWallet.address),
permissions: [
{
role: 'DIDConnectAgent',
claims: ['authPrincipal', 'profile'], // Limit permissions as needed
},
],
exp: Math.floor(Date.now() / 1000) + 3600, // 1 hour expiration
});
},
});
With this configuration, all authentication requests generated by this authenticator
instance will be in the delegated format.
Wallet Request Structure#
In a delegated flow, the request sent to the user's wallet has a distinct structure to clearly communicate the agent-delegator relationship. The wallet receives two public keys and a signed payload.
Key | Description |
---|---|
| The public key of the delegator. Used to verify the delegation proof. |
| The public key of the agent application. Used to verify the main request payload ( |
| A JWT signed by the agent's private key. |
Here is an example of the decoded authInfo
payload:
{
"action": "responseAuth",
"challenge": "...",
"appInfo": { ... },
"requestedClaims": [ ... ],
"iss": "did:abt:z1S...",
"agentDid": "did:abt:zN4...",
"verifiableClaims": [
{
"type": "certificate",
"content": "eyJ..."
}
],
"iat": 1678886400,
"exp": 1678890000
}
Key fields in the payload:
iss
: The DID of the delegator, indicating who is making the request.agentDid
: The DID of the agent, indicating who is performing the action.verifiableClaims
: An array containing the delegation JWT (content
), which proves the agent's authority.
The user's wallet performs a two-step verification: it first checks that the authInfo
JWT is validly signed by the agentPk
. Then, it inspects the verifiableClaims
to confirm that the delegator
(identified by iss
) has authorized the agentDid
by verifying the enclosed delegation JWT against the appPk
.
By using Delegated Connect, you can build applications with stronger security postures by separating roles and permissions. This feature is fully composable with other DID Connect capabilities. To build more complex user journeys, continue to the Chaining Workflows guide to learn how to create multi-step interactions.