Implement a Seamless Data Sharing Flow with Affinidi Iota Framework
After setting up the Affinidi Iota Framework Configuration and the presentation definitions to query the data from Affinidi Vault, enable requesting data on your website using the Affinidi TDK.
In this example, we will request the user’s Email Address from the Affinidi Vault using the Redirect mode to handle data-sharing requests. Follow the sample code below using NextJS to implement the Affinidi Iota Framework on your website.
Important Note on Data Sharing Mode
If you have selected the WebSocket mode to handle data-sharing request, the Decentralised Identifier (DID) of the current user is a required parameter to generate the Iota Credentials to sign the Request Token. To resolve this, implement the Affinidi Login and extract the user’s DID value from the ID Token provided.
You can explore our Labs to learn how to integrate Affinidi Login into different programming languages and frameworks.
Implement Backend Endpoints
To initialise the data-sharing flow of Affinidi Iota Framework and later parse the callback response from Affinidi Vault, we will implement two backend endpoints: one for initialising the data-sharing flow to generate a signed request token and one for parsing the callback response from Affinidi Vault after the user consents to share their data.
Install and Import Backend Dependencies
- Install the
auth-provider
andiota-client
modules in the backend.
npm install -S @affinidi-tdk/auth-provider @affinidi-tdk/iota-client uuid
pip install affinidi_tdk_auth_provider affinidi_tdk_iota_client uuid
- Import the libraries required to generate the Iota Credentials.
import { IotaApi, Configuration, InitiateDataSharingRequestOKData, IotaConfigurationDtoModeEnum, FetchIOTAVPResponseOK } from "@affinidi-tdk/iota-client";
import { AuthProvider } from '@affinidi-tdk/auth-provider';
import { v4 as uuidv4 } from "uuid";
import affinidi_tdk_iota_client
import affinidi_tdk_auth_provider
import json
import uuid
Generate a Signed Request Token
To sign the request token and initiate the Affinidi Vault request, implement a backend service that exposes an API endpoint and returns a signed request token from the Affinidi Iota Framework service. You may use frameworks like Express and NextJS to implement the backend.
Let’s assume the API endpoint is
{BACKEND_SERVICE_URL}/api/iota/init-share
, which the client side will later call using a POST method.
- Generate authorisation token and call the initiateDataSharingRequest method from Iota client of Affinidi TDK.
Initialising Auth Provider
Use the Affinidi CLI Token command to generate the Personal Access Token (PAT) required by the auth-provider
module.
If you have created the Personal Access Token (PAT) with the
--key-id
flag in Affinidi CLI, you must also set thekeyId
with the supplied value in the AuthProvider class.
// NOTE: set your variables for PAT
const privateKey = '<PAT_PRIVATE_KEY_STRING>'
const passphrase = '<PAT_PASSPHRASE>'
const tokenId = '<PAT_ID>'
const projectId = '<PROJECT_ID>'
const authProvider = new AuthProvider({
privateKey,
passphrase,
tokenId,
projectId
})
const api = new IotaApi(
new Configuration({
apiKey: authProvider.fetchProjectScopedToken.bind(authProvider),
})
);
const { data: dataSharingRequestResponse } =
await api.initiateDataSharingRequest({
configurationId,
mode: IotaConfigurationDtoModeEnum.Redirect,
queryId,
correlationId: uuidv4(),
nonce,
redirectUri,
});
const { correlationId, transactionId, jwt } =
dataSharingRequestResponse.data as InitiateDataSharingRequestOKData;
return { correlationId, transactionId, jwt };
stats = {
tokenId,
passphrase,
privateKey,
projectId,
}
authProvider = affinidi_tdk_auth_provider.AuthProvider(stats)
projectScopedToken = authProvider.fetch_project_scoped_token()
configuration = affinidi_tdk_iota_client.Configuration()
# Configure API key authorization: ProjectTokenAuth
configuration.api_key['ProjectTokenAuth'] = projectScopedToken
with affinidi_tdk_iota_client.ApiClient(configuration) as api_client:
api_instance = affinidi_tdk_iota_client.IotaApi(api_client)
request_json = {
"configurationId": "<IOTA_CONFIGURATION_ID>",
"mode": "redirect",
"queryId": "<IOTA_CONFIGURATION_QUERY_ID>",
"correlationId": uuid.uuid4(),
"nonce": "<NONCE_FROM_FRONTEND>",
"redirectUri": "<IOTA_CONFIGURATION_REDIRECT_URL>",
}
initiate_data_sharing_request_input = affinidi_tdk_iota_client.InitiateDataSharingRequestInput.from_dict(request_json)
initiateDataSharingRequestResponse = affinidi_tdk_iota_client.initiate_data_sharing_request(initiate_data_sharing_request_input)
token_request_response = {
"correlationId": initiateDataSharingRequestResponse.data.correlationId,
"transactionId": initiateDataSharingRequestResponse.data.transactionId,
"jwt": initiateDataSharingRequestResponse.data.jwt
}
return json.dumps(token_request_response)
In this method, it requires the following parameters to be able to initiate the data-sharing request:
configurationId
is the ID of the Affinidi Iota Framework configuration you created previously.mode
is the data-sharing flow mode you would like to use to handle the request; in this case, we are using Redirect mode.queryId
is the Presentation Definition configured on the Affinidi Iota Framework configuration to query data from Affinidi Vault.correlationId
which is the UUID value that you can generate using UUID libraries. This value needs to be random on every request.nonce
is a random string value that will be used to validate the response later.redirectUri
is one of the redirect URLs configured on the Affinidi Iota Framework configuration. It should match exactly the value specified in the Redirect URL.
The front end will use the signed request token to generate the share link and automatically redirect the user to the Affinidi Vault to request the data. The signed request contains the Presentation Definition to query the data from Affinidi Vault and request user consent.
Fetch Callback Response
After the user is redirected to the specified Redirect URL from the consent screen of the Affinidi Vault, we have to parse the callback response. To do this, we will implement a backend endpoint that parses the callback sent to the Affinidi Iota Framework.
Let’s assume the API endpoint is
{BACKEND_SERVICE_URL}/api/iota/iota-response
, which the client side will later call using a POST method.
// NOTE: set your variables for PAT
const privateKey = '<PAT_PRIVATE_KEY_STRING>'
const passphrase = '<PAT_PASSPHRASE>'
const tokenId = '<PAT_ID>'
const projectId = '<PROJECT_ID>'
const authProvider = new AuthProvider({
privateKey,
passphrase,
tokenId,
projectId
})
const api = new IotaApi(
new Configuration({
apiKey: authProvider.fetchProjectScopedToken.bind(authProvider),
})
);
const iotaVpResponse: FetchIOTAVPResponseOK = await api.fetchIotaVpResponse({
configurationId: "<IOTA_CONFIGURATION_ID>",
correlationId: "<CORRELATION_ID_FROM_LOCAL_STORAGE>",
transactionId: "<TRANSACTION_ID_FROM_LOCAL_STORAGE>",
responseCode: "<RESPONSE_CODE_FROM_REDIRECT>",
});
const vp = JSON.parse((iotaVpResponse.data as any).vpToken);
return { vp: vp, nonce: iotaVpResponse.data.nonce };
stats = {
tokenId,
passphrase,
privateKey,
projectId,
}
authProvider = affinidi_tdk_auth_provider.AuthProvider(stats)
projectScopedToken = authProvider.fetch_project_scoped_token()
configuration = affinidi_tdk_iota_client.Configuration()
# Configure API key authorization: ProjectTokenAuth
configuration.api_key['ProjectTokenAuth'] = projectScopedToken
with affinidi_tdk_iota_client.ApiClient(configuration) as api_client:
api_instance = affinidi_tdk_iota_client.IotaApi(api_client)
request_json = {
"configurationId": "<IOTA_CONFIGURATION_ID>",
"correlationId": "<CORRELATION_ID_FROM_LOCAL_STORAGE>",
"transactionId": "<TRANSACTION_ID_FROM_LOCAL_STORAGE>",
"responseCode": "<RESPONSE_CODE_FROM_REDIRECT>",
}
iotaVPResponse = affinidi_tdk_iota_client.fetch_iota_vp_response(request_json)
return json.dumps(iotaVPResponse)
In this method, it requires the following parameters to parse the callback response from Affinidi Vault:
configurationId
is the ID of the Affinidi Iota Framework configuration you created previously.correlationId
is the ID returned by the initiateDataSharingRequest method when we generate the signed request token.transactionId
is the ID returned by the initiateDataSharingRequest method when we generate the signed request token.responseCode
is the code returned when the user is redirected back to the website with the specified Redirect URL.
The front end will call this endpoint after the user is redirected to the specified Redirect URL to parse the callback response from Affinidi Vault after the user consents to share their data or when they decline the request.
The endpoint will return the nonce value that the front end can validate if it matches the value it generated when it initialised the data-sharing flow and the JSON data that contains either the Verifiable Presentation token (VP token) data shared by the user or an error message.
Implement Frontend Pages
To enable the data-sharing flow on your website, implement a page that initialises the flow and another page to which the user is redirected after consenting to share their data from Affinidi Vault.
Install and Import Frontend Dependencies
- Install the
common
anduuid
modules in the frontend. We use theuuid
library to generate the nonce required to initiate redirect flow.
npm install -S @affinidi-tdk/common uuid
- Import the libraries required to generate nonce and initialise data-sharing flow.
import { VaultUtils } from '@affinidi-tdk/common';
import { v4 as uuidv4 } from "uuid";
Initiate Data Sharing Request
Create an async function that triggers the Affinidi Iota Framework data-sharing flow and gets the signed request token through an API call from the backend API created from the previous steps to redirect the user to the Affinidi Vault.
async function handleRedirectFlowShare(queryId: string) {
const nonce = uuidv4().slice(0, 10);
const response = await fetch("<BACKEND_SERVICE_URL>/api/iota/init-share", {
method: "POST",
body: JSON.stringify({
queryId: "<IOTA_CONFIGURATION_QUERY_ID>",
nonce: nonce,
}),
headers: {
"Content-Type": "application/json",
"Accept": "application/json",
},
});
const data = await response.json();
const toStore = {
nonce,
correlationId: data.correlationId,
transactionId: data.transactionId,
}
localStorage.setItem('iotaRedirect', JSON.stringify(toStore));
const vaultLink = VaultUtils.buildShareLink(data.jwt, 'client_id');
router.push(vaultLink)
}
In the above code, we are storing the response from the init-share endpoint in the browser’s local storage to use later to fetch the callback response from Affinidi Vault.
After that, attach the async function to a button that users can click to trigger the data-sharing flow and share their data based on your query, or it can be automatically triggered upon loading the page.
<Button
onClick={() => handleRedirectFlowShare(selectedQuery)}
>
Share
</Button>
After successfully initiating the data-sharing flow and generating the Request Token required for the Affinidi Vault, the website redirects the user to the Affinidi Vault consent screen.
Fetch Data after Redirect
Once the user consents to allow access to the requested data, the Affinidi Vault redirects the user back to the specified Redirect URL. In this Redirect URL, we will invoke the iota-response endpoint to parse the callback from Affinidi Vault.
const response = await fetch("/api/iota/iota-response", {
method: "POST",
body: JSON.stringify(params),
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
});
jsonResponse = await response.json();
If the user consent to share their data, the sample Verifiable Presentation token (VP token) response will be in JSON format and contain the requested data:
{
"correlationId": "ab0a1309-bec8-4c28-828f-8a1c7e8ce606",
"presentationSubmission": {
"descriptor_map": [
{
"id": "email_vc",
"path": "$.verifiableCredential[0]",
"format": "ldp_vc"
}
],
"id": "Ub2yNyMEFlW8ziKMKwVzk",
"definition_id": "token_with_email_vc"
},
"nonce": "6c877dd0-a844-4981-be15-aceed1d2fd5c",
"vpToken": {
"@context": [
"https://www.w3.org/2018/credentials/v1"
],
"type": [
"VerifiablePresentation"
],
"verifiableCredential": [
{
"@context": [
"https://www.w3.org/2018/credentials/v1",
"https://schema.affinidi.com/EmailV1-0.jsonld"
],
"id": "claimId:0ecab5796c9f160b",
"type": [
"VerifiableCredential",
"Email"
],
"holder": {
"id": "did:key:zQ3shq75QjFhLLeVCC5MATFdSQPrfkDwHoXBUPKGScHSdWi8p"
},
"credentialSubject": {
"email": "info@affinidi.com"
},
"credentialSchema": {
"id": "https://schema.affinidi.com/EmailV1-0.json",
"type": "JsonSchemaValidator2018"
},
"issuanceDate": "2024-07-29T05:25:57.982Z",
"issuer": "did:key:zQ3shXLA2cHanJgCUsDfXxBi2BGnMLArHVz5NWoC9axr8pEy7",
"proof": {
...
}
}
],
"holder": {
"id": "did:key:zQ3shq75QjFhLLeVCC5MATFdSQPrfkDwHoXBUPKGScHSdWi8p"
},
"id": "claimId:O81O9IXOs-uKQcwctMpEb",
"proof": {
...
}
}
}
Parse the verifiableCredential
returned by the Redirect flow to extract the requested data from the Affinidi Vault shared by the user. The verifiableCredential
can have multiple entries if more than one credentials are requested and shared by the user.
If the user decline to share their data, an access_denied
error is returned.
Learn more about the Affinidi Iota Framework and how the integration works using the sample applications for WebSocket or Redirect flow implementation.
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.