Accepting New Credentials

Accept and store incoming verifiable credentials from Issuers, and manage credentials mid-issuance.

The CredentialExchangeModule provides the functionality needed to easily walk through the Decentralized Identity protocol flow for accepting and storing W3C, IETF SD-JWT and Anoncreds credentials. This module contains support for both Aries/DIDComm-based credential exchanges (e.g., after establishing an E2EE DIDComm connection with an issuer), and for OpenID4VC-based credential exchanges.

The functionality of the CredentialExchangeModule is accessed via the agent's field agent.credentials.exchange. The functionality provided is described below.

Receive a Credential Offer

A CredentialExchange (a pending credential) data object is created in the agent's wallet once a credential offer is received. A CredentialExchange can represent either an Aries or OpenID4VC protocol, this distinction is made via the variant of the CredentialExchange (CredentialExchange.Aries or CredentialExchange.OpenId4Vc). Each of these variants contain further data specific to the protocol being used.

How a CredentialExchange is received by the Edge Agent depends on the protocol being used (see below).

However, regardless of the protocol, after an offer is processed by the agent, a CredentialExchange is created in the agent's wallet and can be accessed and used by the APIs mentioned below. If the agent is subscribed to agent events (see here), a new CredentialExchange will invoke a credential update event.

Receiving an Aries Credential Offer

The agent receives Aries credential offers as encrypted DIDComm messages, either through the receiveMessage API, or via the the MessageSource of the agent's run loop. The credential offer must come from an established connection, and the CredentialExchange.Aries will contain information about which connection the offer came from.

Additionally, CredentialExchange.Aries will show the metadata of what credential is being exchanged, and what the attributes/values of that credential are. These attributes/values can be reviewed by consumers before accepting the offer.

Aries Credential Exchange Formats

The Edge Agent supports multiple formats of credential exchanges (see Standards and Protocols). To represent this, the CredentialExchange.Aries contains a formatData field. This field is a AriesCredentialExchangeFormatData data structure which has variants for the different types of supported exchange formats. Currently these format variants include:

  • .Anoncred: contains the details of a credential exchange being performed with Aries Anoncreds or Indy Attachments. The data format is used to exchange Anoncreds credentials.

  • .AriesLdProof: contains the details of a credential exchange being performed with Aries LD Proof Attachments. The data format is used to exchange W3C VCs (LD Proof) credentials.

Receiving an OpenID4VC Credential Offer

The agent is capable of receiving OpenID4VC credential offers via the openid-credential-offer:// URL scheme through the receiveUrl API. The CredentialExchange.OpenId4Vc will contain details about:

  • the URL of the OpenID4VC Issuer

  • the credential configurations that are being offered by the issuer (i.e. what credential types and formats can be accepted)

  • the required authorization that the agent must fulfil before accepting the offer and receiving a credential

  • any specified display details for the credential configurations and the issuer (e.g., logo, colours).

These details can be reviewed by consumers before authorizing and accepting the exchange.

OpenID4VC Credential Configuration Formats

Like Aries exchanges, OpenID4VC exchanges support different formats of credentials to be exchanged. This is done via the offered configurations: OpenId4VcCredentialConfiguration. Each configuration is returned as a data structure with variants for the different types of supported formats it may represent. Currently these format variants include:

  • .SdJwtVc: contains the details of an IETF SD-JWT VC being offered in the exchange.

Authorize a Credential Exchange (OpenID4VC Only)

Unique to OpenID4VC, after the credential offer is received, the CredentialExchange.OpenId4Vc is initially in the UNAUTHORIZED state, and it must be advanced into the AUTHORIZED state before the offer is ready to accept. The authorizeExchange API (via .openId4Vc) is used for this purpose.

This API takes the identifier of the CredentialExchange.OpenId4Vc to authorize, and a configuration for how the agent should authorize with the issuer.

The OpenId4VcAuthorizeConfiguration configuration has variants for the different kinds of authorization methods. The chosen method should match the permitted authorization variants specified by the issuer in CredentialExchange.OpenId4Vc.requiredAuthorization.

The OpenId4VcAuthorizeConfiguration variants include:

  • .WithPreAuthorization - authorization using pre-authorization flow. In this flow, the authorization is already baked-in to the offer that was originally received, the only extra data that may be required for submission in this configuration is txCode - a PIN-like string which the issuer may require to be provided. This input is required if the txCodeRequired field of RequiredAuthorization.PreAuthorized is non-null.

  • 🚧 Authorization flow coming soon...

let id: String // ID of the cred exchange to authorize ([CredentialExchange.credentialExchangeId])
let txCode: String? // collected TX-Code (e.g. from InputField) if a txCode was required

do {
    try await agent.credentials.exchange.openId4Vc.authorizeExchange(
        credentialExchangeId: id,
        configuration: .withPreAuthorization(txCode: txCode)
    )
} catch {
    // handle error
}

After successfully calling this API, the CredentialExchange.OpenId4Vc will enter the AUTHORIZED state, ready to accept the offer.

Accepting a Credential Offer

With a CredentialExchange.Aries in the OFFER state or a CredentialExchange.OpenId4Vc in the AUTHORIZED state, the agent can "accept" that offer, which will request a credential from the issuer. Both Aries and OpenID4VC exchanges use the same acceptOffer API for this action, however the behaviour and provided configuration is different depending on the protocol.

The acceptOffer API takes the identifier of the CredentialExchange to accept, and a configuration for how the agent should behave in accepting the exchange. This configuration has variants for Aries and OpenID4VC. The difference in behaviour and configuration is described below.

Accepting an Aries Credential Offer

To accept an Aries exchange, the AcceptCredentialOfferConfiguration.Aries configuration should be used. It allows the following behaviour to be adjusted via it's parameters:

  • autoStoreCredential - a flag for whether the credential should be automatically stored in the wallet once the Issuer responds with the issued credential. If false, then the consumer is responsible for calling the Store Credential API once the CredentialExchange receives the "issued" state.

  • storeCredentialConfiguration - a configuration for how the credential should be auto-stored (if autoStoreCredential is configured as true). Allows for custom behaviour, such as automatically applying certain tags to the stored Credential.

  • formatSpecificConfiguration - configuration which is specific to the Aries credential exchange format. This configuration has variants for the different formats which maps to the CredentialExchange.Aries.formatData field. These variants include:

    • .AriesLdProofVc - configuration which can be applied when the credential exchange format is an Aries LD Proof

For Aries exchanges, after calling acceptOffer the credential has not yet been issued nor stored. The Issuer will still have to approve the request and respond again with the issued credential data. The status updates of the issuance process can be tracked via agent events.

Accepting an Aries Anoncred Credential

To accept an Anoncred credential offer using the .Anoncred credential exchange format, no additional formatSpecificConfiguration is required. The following can be done:

let id: String // ID of the cred exchange to accept ([CredentialExchange.credentialExchangeId])

do {
    let configuration = AcceptCredentialOfferConfiguration.aries(.init())
    try await agent.credentials.exchange.acceptOffer(
        credentialExchangeId: id,
        configuration: configuration
    )
} catch {
    // handle error
}

Accept an Aries W3C Credential

To accept a W3C credential offer using the .AriesLdProof credential exchange format, the formatSpecificConfiguration field can be provided with additional configuration. This additional configuration includes:

  • overrideCredentialSubjectId - if provided, request that the issuer uses this string as the Credential Subject ID of the W3C VC. This mechanism can be used to inform the issuer of the holder DID the Edge Agent wants to VC to be bound to. See Manage DIDs.

To accept a W3C and request that the Edge Agent's DID be bound to the credential, the following can be done:

let id: String // ID of the cred exchange to accept ([CredentialExchange.credentialExchangeId])

do {
    // alternatively, an existing DID may be reused.
    let newDid = try await agent.dids.createDid(
        options: .didKey(keyType: .ed25519)
    )
    
    let configuration = AcceptCredentialOfferConfiguration.aries(.init(
        formatSpecificConfiguration: .ariesLdProofVc(
            overrideCredentialSubjectId: newDid.did
        )
    ))
    try await agent.credentials.exchange.acceptOffer(
        credentialExchangeId: id,
        configuration: configuration
    )
} catch {
    // handle error
}

In order to prove holder ownership of a W3C credential (i.e. when presenting proof), it must be bound to one of the Edge Agent's DIDs. Hence the importance of this configuration.

Accepting an OpenID4VC Credential Offer

To accept an OpenID4VC exchange, the AcceptCredentialOfferConfiguration.OpenId4Vc configuration should be used. It allows the following behaviour to be adjusted via it's parameters:

  • autoStoreCredential - see Accepting an Aries Credential Offer

  • storeCredentialConfiguration - see Accepting an Aries Credential Offer

  • credentialConfigurationId - the key of the credential configuration to request be issued. This must be one of the offered keys from .offeredCredentialConfigurations

  • holderBinding - OpenId4VcBindingMethod configuration which specifies how the issuer should bind the requested credential to the agent. The selected binding method should match what was permitted by the issuer in the allowedBindingMethods of the configuration being requested. Examples with supported binding methods are given below.

For OpenID4VC exchanges, the credential is issued and ready to store and calling acceptOffer (or stored if auto-store is configured). The issued credentials can be previewed via the issuedCredentialPreviews field of the exchange prior to storage.

Accepting an OpenID4VC Exchange with DID Binding

The .WithDid OpenId4VcBindingMethod configuration can be used to request that the issued credential be bound to a particular DID owned by the Edge Agent (See Manage DIDs). This method should be used with a DID that has a DID method and key type that satisfies what is specified in the credential configuration's allowedBindingMethods sub-fields.

let id: String // ID of the cred exchange to accept ([CredentialExchange.credentialExchangeId])
let credentialConfigurationId: String // The credential configuration ID/key to request

do {
    // alternatively, an existing DID may be reused.
    // assumes did:key DID method and P256 key type supported
    let newDid = try await agent.dids.createDid(
        options: .didKey(keyType: .p256)
    )
    
    let configuration = AcceptCredentialOfferConfiguration.openId4Vc(.init(
        credentialConfigurationId: credentialConfigurationId,
        holderBinding: .withDid(did: newDid.did)
    ))
    try await agent.credentials.exchange.acceptOffer(
        credentialExchangeId: id,
        configuration: configuration
    )
} catch {
    // handle error
}

Store a Credential

When a CredentialExchange reaches the ISSUED state, and has not been configured to auto-store, then the agent must call the storeCredential API before the Credential is stored in the wallet, completing the protocol.

The API takes the identifier of the CredentialExchange to store the credential of, and a configuration for how the agent should behave when storing the credential.

The StoreCredentialConfiguration data object can be used to overwrite tags that the Credential will be stored with. It could also be left as null to leave the tags as their default.

The API will also return the new Credential that was stored as a result of the API call. This new credential will also now be available in the agent's Credential management module.

let id: String // ID of the cred exchange to store ([CredentialExchange.credentialExchangeId])

do {
    let configuration = StoreCredentialConfiguration()
    let newCredential: Credential = try await agent.credentials.exchange.storeCredential(
        credentialExchangeId: id,
        configuration: configuration
    )
} catch {
    // handle error
}

There is no need to call this API if the agent is configured to auto-store credentials.

Get a Pending Credential by ID

As mentioned above, a credential pending issuance is represented by CredentialExchange objects. To retrieve the current state of a specific CredentialExchange in the agent's wallet, the getById API can be used. If a credential exchange cannot be found by the given identifier, then null is returned:

let id: String // ID of the cred exchange to get ([CredentialExchange.credentialExchangeId])

do {
    let credentialExchange = try await agent.credentials.exchange.getById(credentialExchangeId: id)
} catch {
    // handle error
}

Delete a Pending Credential by ID

Similarly, a CredentialExchange in the wallet can be easily deleted via the deleteById API:

let id: String // ID of the cred exchange to delete ([CredentialExchange.credentialExchangeId])

do {
    try await agent.credentials.exchange.deleteById(credentialExchangeId: id)
} catch {
    // handle error
}

Updating the Metadata of a Pending Credential

CredentialExchange objects contain some metadata that can be controlled by SDK consumers, allowing custom information to be attached to each CredentialExchange, and allowing custom listing functionality to be leveraged.

Each CredentialExchange contains a list of RecordTag (CredentialExchange.tags) attached to it, where a RecordTag is simply a name-value pair stored with the record. By default, some tags are attached to a new CredentialExchange, this includes:

  • tag-name: ~started_timestamp, tag-value: The UNIX epoch seconds which this credential began exchange

The tags on a CredentialExchange can be replaced or updated by using the updateCredentialExchange API, and providing a new set to update. This will replace whatever the current set of tags is:

let id: String // ID of the credential exchange to update

// add a 'category' of 'work' to this credential, and a 'priority' of '1'
let update = CredentialExchangeUpdate(tags: [
    RecordTag(name: "category", value: "work"),
    RecordTag(name: "~priority", value: "1")
])

do {
    try await agent.credentials.exchange.updateCredentialExchange(credentialExchangeId: id, credentialExchangeUpdate: update)
} catch {
    // handle error
}

Like most data in the wallet, RecordTag will be stored encrypted. Unless, the tag name is prefixed with ~, then the tag value will be stored unencrypted. Storing a tag value as unencrypted will allow some additional listing queries to be performed (see below).

Listing Pending Credentials

To list all pending credentials in the agent's wallet, the listAll API can be used:

do {
    let credentialExchanges = try await agent.credentials.exchange.listAll(options: nil)
} catch {
    // handle error
}

Filtered Listing

More complicated CredentialExchange list queries can also be achieved by utilizing the ListCredentialExchangeFilters.

These filters allow for a list of CredentialExchange to be filtered by their state, tags, or both together.

Filtering by state can be achieved as follows.

// get all the pending credentials which are waiting for their offer to be accepted
let filters = ListCredentialExchangeFilters(state: CredentialExchangeState.offer)
let options = ListCredentialExchangeOptions(filters: filters)
do {
    let credentialExchanges = try await agent.credentials.exchange.listAll(options: options)
} catch {
    // handle error
}

To filter by tags applied to the CredentialExchange (i.e. applied via the update API), the tagFilter field of ListCredentialExchangeFilters should be used. This field takes a String in compliance with a Wallet Query Language (WQL) Query.

Continuing from the example in the Update API section:

// WQL Query, filter for 'category' == 'work'
let wqlQuery = "{ \"category\": \"work\" }"
let filters = ListCredentialExchangeFilters(tagFilter: wqlQuery)
let options = ListCredentialExchangeOptions(filters: filters)
do {
    let pendingWorkCredentials = try await agent.credentials.exchange.listAll(options: options)
} catch {
    // handle error
}
// WQL Query, filter for priority < 2 (e.g. 'high' priority items)
let wqlQuery = "{ \"~priority\": { \"$lt\": \"2\" } }"
let filters = ListCredentialExchangeFilters(tagFilter: wqlQuery)
let options = ListCredentialExchangeOptions(filters: filters)

do {
    let highPriorityPendingCredentials = try await agent.credentials.exchange.listAll(options: options)
} catch {
    // Handle error
}

Last updated