Sample Application with Credential Issuance
Before you begin
- Set up Affinidi Vault account. Follow the guide below if you haven’t set it up yet.
- Install the Affinidi CLI. Follow the guide below if it hasn’t been installed.
Make sure you have Git installed on your machine. Follow this guide on how to install Git.
Install the NodeJS on your machine if you haven’t set it up yet.
Download the Application
You can download as ZIP file the code sample from the GitHub Repo or generate it using Affinidi CLI with the following command:
affinidi generate app --provider=affinidi --framework=nextjs --library=nextauthjs --path=affinidi-sample-app
Select
n
when prompted to Automatically configure sample app environment, we will configure it later.
The above command will generate the code sample in the affinidi-sample-app
directory.
Important Note
The downloadable sample application is provided only as a guide to quickly explore and learn how to integrate the components of Affinidi Trust Network into your application. This is NOT a Production-ready implementation. Do not deploy this to a production environment.Install Dependencies
After successfully generating the sample app, go to the affinidi-sample-app
directory from your terminal and install the required dependencies using the following commands:
npm install
Integrate Affinidi Login
The Credential Issuance requires the Decentralised Identifier (DID) of the user to set the Holder DID (holderDid
) as a parameter when creating a Credential Offer. To get the User DID, we will implement Affinidi Login to authenticate and extract the user DID value from the ID Token provided.
Create Login Configuration
Name: Affinidi Sample App
Redirect URIs: http://localhost:3000/api/auth/callback/affinidi
Login Configuration uses the default Presentation Definition (presentationDefinition) and ID Token Mapping (idTokenMapping) that is used to request the user’s email address during the authentication flow.
Learn more about customising the Presentation Definition and ID Token using this guide.
Important
Safeguard the Client ID and Client Secret diligently; you'll need them for setting up your IdP or OIDC-compliant applications. Remember, the Client Secret will be provided only once.
Configure the App to Enable Affinidi Login
Once the Login Configuration is created, set up the client credentials provided to integrate Affinidi Login.
Copy and set up the environment variables:
cp .env.example .env
Set the following variables with the values provided by the Login Configuration:
PROVIDER_CLIENT_ID="<LoginConfig.auth.ClientID>"
PROVIDER_CLIENT_SECRET="<LoginConfig.auth.ClientSecret>"
PROVIDER_ISSUER="<LoginConfig.auth.Issuer>"
The <LoginConfig.auth.*>
are values from Login Configuration.
Run the Application
After installing the dependencies and setting up the required details in the application, run the following command to start the app locally:
npm run dev
Once it is successfully started, visit the app using the link http://localhost:3000 and click on the Affinidi Login button to test if the Affinidi Login flow is working.
Implement Credential Issuance
After successfully setting up the Affinidi Login, let’s configure the integration to enable credential issuance flow.
Set up Credential Issuance Configuration
To issue a Verifiable Credential, it is required to setup the Issuance Configuration on your project, where you select the issuing wallet and supported schema to create a credential offer that the application issue.
You can easily do this using the Affinidi Portal:
Go to Affinidi Portal and click on the Credential Issuance page.
Click on Create Configuration and set the following fields:
Issuing Wallet: Create a new wallet and provide the new wallet name or select an existing Wallet that will sign and issue the credentials to the user. Read more about Wallets here.
Lifetime of Credential Offer: Credential Offers have a limited lifetime to enhance security. Consumers must claim the offer within this timeframe.
Supported Schemas: List of allowed schemas for creating a credential offer. Issuance will validate the credential data based on the supported schema before creating the Credential Offer. Create the Schema through the Schema Builder.
The Credential Type ID
specified on each supported schema is used as a reference when creating a Credential Offer.
- After setting the fields and providing the list of the supported schema, click Create.
Note
Currently, the Credential Issuance Service only supports one Issuance Configuration per Project.Create a Personal Access Token
After creating the Credential Issuance configuration and the selecting the supported Schemas to issue credential to the the Affinidi Vault, we’ll create a Personal Access Token (PAT), a machine user that authenticates to the services and performs actions on your behalf.
To create a Personal Access Token (PAT), run the following Affinidi CLI command:
affinidi token create-token -n IssuanceToken --auto-generate-key --passphrase "MySecretPassphrase" --with-permissions
The above command will create a Personal Access Token (PAT), including private/public keys protected with a passphrase and a defined policy to grant the PAT permission to perform actions on your behalf, in this case, calling Credential Issuance service.
Copy the command output, and we’ll configure it later on the environment variables of the sample application — the sample output of the command:
{
"tokenId": "086b64ce-oau0-huy7-9876-65d76e84fa36",
"projectId": "cba41b90-34bg-jhg9-acgf-800baa9a40a0",
"privateKey": "...",
"passphrase": "MySecretPassphrase"
}
Important Note
Keep your private key and passphrase secured; you’ll need them to integrate with Affinidi TDK. Remember, the Private Key and Passphrase will be provided only once.Configure the App to Integrate with Affinidi TDK
Let’s configure the Personal Access Token (PAT) details into the environment variables of the sample application.
Set the following variables with the values provided by the Personal Access Token (PAT):
PROJECT_ID="<PAT.projectId>"
TOKEN_ID="<PAT.tokenId>"
PRIVATE_KEY="<PAT.privateKey>"
PASSPHRASE="<PAT.passphrase>"
Keep the
PUBLIC_KEY
andKEY_ID
inenv
file empty.
Try the Credential Issuance Flow
After successfully setting up the Personal Access Token (PAT) details and running the application, go to the web page at http://localhost:3000 and click the Issue Credentials page. At this point, you should have already been logged in. If you are not yet logged in, click on the Affinidi Login button.
On the Issue Credentials page, you will see your Decentralised Identifier (DID) from the ID Token of Affinidi Login and a dropdown field listing all the available Credential Issuance configurations created on your project.
Select the Credential Issuance configuration, including the Claim Mode on how the user can claim the credential, whether a TX_CODE that generates a Transaction Code that users must enter to claim the credential or a normal mode without Transaction Code required. Also, select the Schema from the Supported Schema listed on your configuration to create a credential offer.
The page will dynamically render the fields based on the selected Schema to enter the details for the credential.
How the Integration Works
After configuring and testing the credential issuance flow from the sample app, let’s review the codes and how they work to enable this flow.
We are using Affinidi TDK and some of its modules to enable this.
Auth Provider package to authenticate and authorise the Personal Access Token (PAT) to perform actions on your behalf.
Credential Issuance client library to integrate with the Credential Issuance service of Affinidi Elements.
Read more about Affinidi TDK here.
Handle Credential Data Submission
The frontend implementation of the Credential Issuance calls a client library that triggers credential issuance based on the selected configuration, claim mode, and the Schema. The credential data is based on the list of fields of the selected Schema from the list of supported schemas.
Source Path: src/pages/credential-issuance.tsx
const handleSubmit = async (credentialData: any) => {
console.log("credentialData:", credentialData);
if (
!holderDid &&
claimMode == StartIssuanceInputClaimModeEnum.FixedHolder
) {
setMessage({
message: "Holder DID is required in FIXED_DID claim mode",
type: "error",
});
return;
}
setIsFormDisabled(true);
const response = await fetch("/api/issuance/start", {
method: "POST",
body: JSON.stringify({
holderDid,
credentialData,
credentialTypeId: selectedTypeId,
claimMode,
}),
headers: {
"Content-Type": "application/json",
},
});
if (!response.ok) {
clearIssuance();
setMessage({
message: "Error creating offer",
type: "error",
});
return;
}
let dataResponse = await response.json();
if (dataResponse.credentialOfferUri) {
setOffer(dataResponse);
}
console.log("Offer", offer);
};
Prepare Credential Issuance Payload
A backend implementation of the Credential Issuance flow exposes an API endpoint that takes the selected configurations, including the credential data provided from the front end and prepares the payload. This endpoint verifies if an active user session was created from the Affinidi Login authentication flow.
The user session contains the Decentralised Identifier (DID) of the logged-in user and is added as part of the payload to set the recipient of the credential offer.
Source Path: src/pages/api/issuance/start.ts
try {
const { credentialTypeId, credentialData, claimMode, holderDid } =
issuanceStartSchema.parse(req.body);
const apiData: StartIssuanceInput = {
claimMode,
...(holderDid && { holderDid }),
data: [
{
credentialTypeId,
credentialData: {
...credentialData,
// Add any additional data here
},
},
],
};
const issuanceResult = await startIssuance(apiData);
res.status(200).json(issuanceResult);
} catch (error: any) {
res.status(500).json({ message: "Unable to start issuance" });
console.log(error);
}
Create a Credential Offer
After preparing the payload to create a credential offer, the endpoint from the previous section calls the Credential Issuance client to trigger the issuance flow and create a credential offer. In this client file, we have created an async function that generates an authorisation token through the AuthProvider
package and initialises the Credential Issuance
client of the Affinidi TDK.
Source Path: src/lib/clients/credential-issuance.ts
export async function startIssuance(apiData: StartIssuanceInput) {
const authProvider = getAuthProvider();
const api = new IssuanceApi(
new Configuration({
apiKey: authProvider.fetchProjectScopedToken.bind(authProvider),
}),
);
const { data } = await api.startIssuance(projectId, apiData);
return data;
}
The api.startIssuance
will return the credential offer details in JSON format. This contains the credentialOfferUri, the txCode if you selected TX_CODE as claim mode, and the credential offer’s lifetime.
{
"credentialOfferUri": "https://<PROJECT_ID>.apse1.issuance.affinidi.io/offer/3da42adc-5412-413b-986c-99c43cb87eba",
"txCode": "123456",
"issuanceId": "3da42adc-5412-413b-986c-99c43cb87eba"
"expiresIn": 3600
}
The Affinidi Vault user must claim the credential offer before it expires; otherwise, you will have to create another credential offer.
Generate Claim Link
After creating the credential offer, you can send the claim link via email or QR code to allow the recipient of the credential offer to claim the credential and store it on their Affinidi Vault. The claim link will be in the following format with the URL encoded <CREDENTIAL_OFFER_URI>
value:
https://vault.affinidi.com/claim?credential_offer_uri=<CREDENTIAL_OFFER_URI>
If the Credential Offer was created with the Transaction Code (txCode
), the developer must also send it to the user to claim the credentials.
In this example, we have created another component called Offer
that generates the claim link using the VaultUtils of Affinidi TDK’s common package.
Source Path: src/components/issuance/Offer.tsx
const vaultLink = VaultUtils.buildClaimLink(offer.credentialOfferUri)
Click on the displayed link to open your Affinidi Vault to claim the credential and store it on your selected profile in the Affinidi Vault. Read more about Claimed credentials here.
Glad to hear it! Please tell us how we can improve more.
Sorry to hear that. Please tell us how we can improve.
Thank you for sharing your feedback so we can improve your experience.