A framework that provides a secured and simplified data-sharing process from Affinidi Vault with user consent for enhanced user experience. The Affinidi Iota Framework leverages the OID4VP (OpenID for Verifiable Presentation) standard to request and receive data from Affinidi Vault. The OID4VP is built with the OAuth 2.0 authorisation framework, providing developers with a simple and secure presentation of credentials.
More Details on Affinidi Iota Framework is available on Affinidi Documentation
We will use the same Next.js app which we worked on in modules 1 & module 2 and enable the Affinidi Iota framework in the iota
page with the nextjs
Framework. It implements workflows that ensure users have full control over their data, emphasizing secure and transparent data-sharing practices using OID4VP & PEX.
To enable your website to request data from the user, you must create an Affinidi Iota Framework configuration to set up the Signing Wallet, JWT Expiration, and the Presentation Definitions required to query the data from the Affinidi Vault.
After creating a configuration, you can integrate the Affinidi Iota Framework into your website with Affinidi TDK. This allows you to request and receive user data from their Affinidi Vault.
Content | Description |
---|---|
Pre-Requisite |
Complete the pre-requisite for Affinidi Iota Framework |
install dependencies |
Install dependencies |
create files & folders |
Create required directories and files in the app |
personal access token |
Create personal access token (PAT) |
Affinidi Iota Framework config |
Configure Affinidi Iota Framework |
update .env files |
Update .env files |
update frontend Env reader |
Update frontend env reader file variables.ts files |
create routes & button |
Create /iota page with ssr:false and button |
create component |
Add components IotaClientPage.tsx for rendering the page with Button to Initiate Iota framework |
handleIotaShare function |
Create handleIotaShare function which invoke Iota framework |
create IotaSession |
Add the createIotaSession function |
frontend API for Iota Token |
Create frontend API to pass details for Iota Limited Token |
backend API for Iota Token |
Create the API to start the Iota Limited Token start.ts |
New data request |
Add the addNewDataRequest function |
update data request with response |
Add the updateDataRequestWithResponse function |
Run Application |
Try the App with Affinidi Login & Affinidi Iota Framework |
To complete this workshop, you would require a developer account at Affinidi Portal and a few tools as listed below.
- An account on Affinidi portal
- Affinidi cli
- Implement Affinidi Login in this application (covered in Module 1)
- Implement Affinidi Credentials Issuance in this application (covered in Module 2)
You need to have the following installed on your machine:
- NodeJs v18 and higher. (it's recommended to use nvm)
- VS Code or any familar IDE for development.
More details here in Affinidi Documentation
Important
This sample App is an extension of the same App that we worked on for Affinidi Login and Affinidi Credentials Issuance Service.
Before proceeding with the steps below, make sure you have completed the prerequisites mentioned above.
Warning
The steps showcased in this sample application are 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. Please don't deploy this to a production environment.
Now, let's continue with the step-by-step guide to enable the Affinidi Iota framework in the sample App.
Install Dependencies for Affinidi Iota framework
which is iota-browser
& iota-core
npm install @affinidi-tdk/iota-browser @affinidi-tdk/iota-core
Important
It also requires the Affinidi TDK Auth Provider, which we've already installed with the Affinidi Credential Issuance service in Module 2.
mkdir -p src/components/iota
mkdir -p src/pages/api/iota
touch src/pages/iota.tsx
touch src/components/iota/IotaClientPage.tsx
touch src/pages/api/iota/start.ts
A Personal Access Token (PAT) is like a machine user that acts on your behalf to the Affinidi services. You can use the PAT to authenticate to the Affinidi services and automate specific tasks within your application. A Personal Access Token (PAT) lives outside of Projects, meaning PAT can access multiple projects once granted by the user.
More Details on Personal Access Token here.
Personal Access Token is needed for Affinidi TDK Auth provider
.
You can refer the Affinidi Documentation for creating pesronal access token from CLI.
Important
We already have created a personal access token (PAT) as part of Affinidi Credential Issuance service. we will reuse the same token for the Affinidi Iota framework as well.
create Affinidi Iota framework configuration
You can easily do this using the Affinidi Portal
-
Login on Affinidi Portal
-
Click on Create Configuration and set the following fields:
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.Vault JWT Expiration time
: Credential Offers have a limited lifetime to enhance security. Consumers must claim the offer within this timeframe. -
Optionally, you can configure whether to enable:
Enable Verification
: To verify the credentials the user shares using the Credential Verification service.Enable Consent Audit Log
: To store the consent given by the user whenever they share data with the website. -
After setting the fields and providing the list of the supported schema, click
Create
. -
Provide the name of the Presentation Definition and then select from the available templates to pre-populate the editor. You can just modify the presentation definition template based on the data you would like to request from the Affinidi Vault.
for eg
{
"id": "token_with_workshopSchema",
"input_descriptors": [
{
"id": "workshopSchema",
"name": "Workshop VC",
"purpose": "Check if Vault contains the required VC.",
"constraints": {
"fields": [
{
"path": [
"$.type"
],
"purpose": "Check if VC type is correct",
"filter": {
"type": "array",
"contains": {
"type": "string",
"pattern": "TworkshopSchemaV1R0"
}
}
}
]
}
}
]
}
- Click on Save.
ConfigurationId
&QueryId
is generated.
Important
The above PEX query responds only in case Affinidi Vault has claimed credentials using Affinidi Credential Issuance enabled from the previous workshop steps.
update .env
file with frontend variables
NEXT_PUBLIC_IOTA_QUERY_ID = ""
NEXT_PUBLIC_IOTA_CONFIGURATION_ID = ""
Update Next Auth frontend Variables src/lib/variables.ts
export const configurationId = process.env.NEXT_PUBLIC_IOTA_CONFIGURATION_ID!;
export const queryId = process.env.NEXT_PUBLIC_IOTA_QUERY_ID!;
Create new routes src/pages/iota.tsx
and put dynamic content from Next Auth and SSR : False
import dynamic from "next/dynamic";
const IotaClientPage = dynamic(
() => import("../components/iota/IotaClientPage"),
{
ssr: false,
}
);
export default function Page() {
return <IotaClientPage />;
}
Add components
for rendering the page IotaClientPage
with Button
to Initiate the Affinidi Iota framework
Create a component as Iota Client Page src/components/iota/IotaClientPage.tsx
import { useState } from "react";
import {
IotaCredentials,
IotaRequest,
IotaResponse,
OpenMode,
Session,
} from "@affinidi-tdk/iota-browser";
import { configurationId, queryId } from "@/lib/variables";
type DataRequest = {
request?: IotaRequest,
response?: IotaResponse,
};
export default function IotaTesting() {
const containerStyle: React.CSSProperties = {
backgroundColor: "auto",
height: "100vh",
};
return (
<div style={containerStyle}>
<h1 className="text-2xl font-semibold pb-6">Affinidi Iota Framework</h1>
{/* Step 1: Add a button to trigger the handleIotaShare function */}
<button
className="inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 mr-5"
onClick={handleIotaShare} // Step 2: Add the handleIotaShare function to the button
>
Click to Share
</button>
<button
type="submit"
onClick={() => window.open("/", "_self")}
className="inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
>
Return to Homepage
</button>
{dataRequest?.response && (
<>
<br />
<br />
<h3 className="text-2xl font-semibold pb-6">Response</h3>{" "}
{/* Display the response */}
<pre>{JSON.stringify(dataRequest.response, null, 2)}</pre>
</>
)}
</div>
);
}
Create handleIotaShare
function which invoke Affinidi Iota Framework
const [iotaIsInitializing, setIotaIsInitializing] = useState(false);
const [iotaSession, setIotaSession] = useState<Session | undefined>(
undefined
);
const [dataRequest, setDataRequest] = useState<DataRequest>(); // Data requests and responses
const handleIotaShare = async () => {
try {
setIotaIsInitializing(true);
const session = await createIotaSession();
if (!session) {
throw new Error("IotaSession not initialized");
}
const request = await session.prepareRequest({ queryId });
console.log("Request", request);
addNewDataRequest(request);
request.openVault({ mode: OpenMode.Popup }); // Open the vault in a popup, Option are 1)NewTab, 2)Popup
setIotaIsInitializing(false);
const response = await request.getResponse();
console.log("Response", response);
if (response) {
updateDataRequestWithResponse(response);
}
} catch (e) {
console.error("Error", e);
setIotaIsInitializing(false);
}
};
Add the createIotaSession function
const createIotaSession = async () => {
try {
if (iotaSession) {
console.log("Iota session already exists");
return iotaSession;
}
console.log("======Iota Session Creating=========");
console.log("configurationId", configurationId);
const credentialResponse = await getIotaCredentials(configurationId);
console.log("credentialResponse", credentialResponse);
const session = new Session({ credentials: credentialResponse });
console.log("session", session);
await session.initialize();
setIotaSession(session);
return session;
} catch (error) {
setIotaSession(undefined);
console.error("Error initializing Iota Session:", error);
}
};
Create function getIotaCredentials function
async function getIotaCredentials(configurationId: string) {
const response = await fetch(
"/api/iota/start?" +
new URLSearchParams({
iotaConfigurationId: configurationId,
}),
{
method: "GET",
}
);
return (await response.json()) as IotaCredentials;
}
Create backend API to call Affinidi TDK
for Affinidi Auth Provider and Affinidi Iota Core for creating Iota limited token
Create the API to start the Iota Limited Token src/pages/api/iota/start.ts
import { IotaCredentials, Iota } from "@affinidi-tdk/iota-core";
import type { NextApiRequest, NextApiResponse } from "next";
import { AuthProvider } from "@affinidi-tdk/auth-provider";
import { passphrase, privateKey, projectId, tokenId } from "@/lib/env";
import { getServerSession } from "next-auth";
import { authOptions } from "@/lib/auth/auth-options";
interface ResponseError {
message: string;
}
export default async function handler(
req: NextApiRequest,
res: NextApiResponse<IotaCredentials | ResponseError>
) {
try {
const session = await getServerSession(req, res, authOptions);
if (!session) {
res.status(401).json({ message: "You must be logged in." });
return;
}
const authProvider = new AuthProvider({
privateKey,
passphrase,
tokenId,
projectId,
});
const { iotaConfigurationId } = req.query;
const iotaToken = authProvider.createIotaToken(
iotaConfigurationId as string,
session.userId
);
const iotaCredentials = await Iota.limitedTokenToIotaCredentials(
iotaToken.iotaJwt
);
res.status(200).json(iotaCredentials);
} catch (error: any) {
res.status(500).json({ message: "Unable to get Iota credentials" });
console.log(error);
}
}
Add the addNewDataRequest function
const addNewDataRequest = (request: IotaRequest) => {
setDataRequest((prevRequests) => ({
...prevRequests,
request,
}));
};
Add the updateDataRequestWithResponse function
const updateDataRequestWithResponse = (response: IotaResponse) => {
setDataRequest((prevRequests) => ({
...prevRequests,
response,
}));
};
Try the App with Affinidi Login & Affinidi Iota Framework
npm run dev
Open http://localhost:3000/iota with your browser to see the result.