Skip to main content
Version: v0.4.x

Issue a credential

In this tutorial we will issue a credential from the Issuer to a Holder. We will start with setting up both their agents with the minimal configuration required to follow this tutorial. It is assumed that there is a connection between the Issuer and the Holder and the Issuer also has a registered schema and credential definition. After initializing the Issuer will send a credential to the holder, and will then accept this credential and automatically store it in their wallet.

Using AnonCreds and the Issue Credential V2 Protocol or the Issue Credential V1 Protocol.

info

This section assumes that

  1. You have set-up your develoment environment.
  2. You have basic knowledge of the required fields in the Agent Config
  3. You have completed the Create a Connection tutorial
  4. You have a registered schema and credential definition. This can be done by following the Registering on a AnonCreds Registry

1. Setting up the agents

First for both agents we must setup and initialize an agent to work with. Depending on your target, React Native or Node.js, it might vary.

In this tutorial the Holder will be in a Node.js environment and the Issuer also in a Node.js environment.

Issuer

For the Issuer the setup is commonly not the same as the Holder. In the example they both live in a server environment, meaning they do not need a mediator. More commonly, the Holder is in a mobile environment where a mediator is required for receiving DIDComm messages.

It is also very important for the Issuer to have a public DID, for the binding with a credential definition, amongst other things. For this demo we will use BCovrin Test. If you want to follow this tutorial, you have to register a public DID here via the Wallet seed field (this must be the same as the seed inside the config under the key publicDidSeed).

In order to reach the Issuer we have to add a list of endpoints of the agent that exposes the inboundTransport to the public. In the example below we add an inboundTransport and use port 3002. For development purposes it is recommended to use a tunneling service for this, like Ngrok. Ngrok will allow you to reach your locally exposed endpoint from the public. If a tunneling service is used, make sure to use the HTTPS variant as mobile environments, by default, do not accept HTTP anymore.

To install Ngrok and expose the port to the public the following commands can be used:

yarn global add ngrok

ngrok http <PORT>
📄issuer
const issuer = new Agent({
config: issuerConfig,
dependencies: agentDependencies,
modules: {
askar: new AskarModule({
ariesAskar,
}),
anoncredsRs: new AnonCredsRsModule({
anoncreds,
}),
indyVdr: new IndyVdrModule({
indyVdr,
networks: [
{
isProduction: false,
indyNamespace: 'bcovrin:test',
genesisTransactions: '<genesis transaction>',
connectOnStartup: true,
},
],
}),
anoncreds: new AnonCredsModule({
registries: [new IndyVdrAnonCredsRegistry()],
}),
dids: new DidsModule({
registrars: [new IndyVdrIndyDidRegistrar()],
resolvers: [new IndyVdrIndyDidResolver()],
}),
credentials: new CredentialsModule({
credentialProtocols: [
new V2CredentialProtocol({
credentialFormats: [new LegacyIndyCredentialFormatService(), new AnonCredsCredentialFormatService()],
}),
],
}),
},
})

// Register a simple `WebSocket` outbound transport
issuer.registerOutboundTransport(new WsOutboundTransport())

// Register a simple `Http` outbound transport
issuer.registerOutboundTransport(new HttpOutboundTransport())

// Register a simple `Http` inbound transport
issuer.registerInboundTransport(new HttpInboundTransport({ port: 3002 }))

Holder

For the Holder we need to setup a basic agent with a wallet, mediator, outbound transport and a ledger.

If you want to follow this tutorial in a mobile environment:

  1. Use the agentDependencies from @aries-framework/react-native
  2. It is very important to note that mobile agents do not support HTTP by default. It is recommended to do everything over HTTPS, but for development HTTP can be enabled for iOS and Android.
🗄holder
const holder = new Agent({
config: holderConfig,
dependencies: agentDependencies,
modules: {
askar: new AskarModule({
ariesAskar,
}),
anoncredsRs: new AnonCredsRsModule({
anoncreds,
}),
indyVdr: new IndyVdrModule({
indyVdr,
networks: [
{
isProduction: false,
indyNamespace: 'bcovrin:test',
genesisTransactions: '<genesis transaction>',
connectOnStartup: true,
},
],
}),
anoncreds: new AnonCredsModule({
registries: [new IndyVdrAnonCredsRegistry()],
}),
dids: new DidsModule({
resolvers: [new IndyVdrIndyDidResolver()],
}),
credentials: new CredentialsModule({
credentialProtocols: [
new V2CredentialProtocol({
credentialFormats: [new LegacyIndyCredentialFormatService(), new AnonCredsCredentialFormatService()],
}),
],
}),
},
})

// Register a simple `WebSocket` outbound transport
holder.registerOutboundTransport(new WsOutboundTransport())

// Register a simple `Http` outbound transport
holder.registerOutboundTransport(new HttpOutboundTransport())

// Register a simple `Http` inbound transport
holder.registerInboundTransport(new HttpInboundTransport({ port: 3002 }))

3. Listening for incoming credentials

When we want to accept a credential, we have to listen to incoming credentials and handle accordingly. In this example we do not have any user interaction, but is likely that your application would have a user-interface which would display the credential. When receiving a credential offer you can get the values from credentialExchangeRecord.credentialAttributes.

🗄holder
holder.events.on<CredentialStateChangedEvent>(CredentialEventTypes.CredentialStateChanged, async ({ payload }) => {
switch (payload.credentialRecord.state) {
case CredentialState.OfferReceived:
console.log('received a credential')
// custom logic here
await holder.credentials.acceptOffer({ credentialRecordId: payload.credentialRecord.id })
case CredentialState.Done:
console.log(`Credential for credential id ${payload.credentialRecord.id} is accepted`)
// For demo purposes we exit the program here.
process.exit(0)
}
})

4. Issuing a credential

Now that everything is setup on both sides, the Issuer can now offer a credential to the Holder.

In this example we do not instantiate a connection and assume that there is one. Please refer to this guide Create a connection to get a connection and connectionId.

📄issuer
const indyCredentialExchangeRecord = await issuer.credentials.offerCredential({
protocolVersion: 'v2',
connectionId: '<connection id>',
credentialFormats: {
indy: {
credentialDefinitionId: '<credential definition id>',
attributes: [
{ name: 'name', value: 'Jane Doe' },
{ name: 'age', value: '23' },
],
},
},
})

Useful resources