Sample App with Redirect Implementation

Use this guide to setup and run the sample application using NextJS that integrates with Affinidi Iota Framework using the Redirect mode to get you started quickly.

This guide provides details on how to enable a consent-driven data sharing flow using the Redirect mode of the Affinidi Iota Framework. Using the Redirect mode, the application is not required to implement the Affinidi Login and uses the Redirect URL to generate a signed request token to initiate data-sharing on your application.

Before you begin
  1. Set up Affinidi Vault account. Follow the guide below if you haven’t set it up yet.
Set up Affinidi Vault

Set up an Affinidi Vault account using the Web Vault or install the Mobile Vault (for Android).

The same setup steps for Mobile Vault.

  1. Click on Get started if you are creating a new account, or click on Restore from Backup if you have an existing backup of your Affinidi Vault. Provide the Passphrase to secure your Affinidi Vault.

You have the option to enable Biometrics to unlock your Affinidi Vault easily instead of using Passphrase.

Affinidi Vault Setup
  1. Enter your email address to register with the Affinidi Vault. An OTP will be sent to this email for verification.
Affinidi Vault Passphrase
  1. Enter the OTP sent to the email you have provided for verification to complete the setup.
Affinidi Vault Email Verification

After successfully providing the OTP, you are redirected to the Affinidi Vault dashboard.

  1. Install the Affinidi CLI. Follow the guide below if it hasn’t been installed.
Set up Affinidi CLI
  1. Download and install NodeJS on your machine if you haven’t set it up yet.
  1. Install Affinidi CLI using Node Package Manager (npm).
npm install -g @affinidi/cli
  1. Verify that the installation is successful.
affinidi --version
  1. Make sure you have Git installed on your machine. Follow this guide on how to install Git.

  2. 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.

Install App 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

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.

Integrate Affinidi Iota Framework

To integrate the Affinidi Iota Framework on your website to enable data-sharing flow using Redirect flow, you must create the Affinidi Iota Framework Configuration and define a Presentation Definition to query the user data from the Affinidi Vault.

Set up Affinidi Iota Framework Configuration

To enable Affinidi Iota Framework on your website, you must create an Affinidi Iota Framework configuration where you select the wallet used to sign the request token and the expiration of the request token, including the Presentation Definition to use to query the data from Vault.

You can easily do this using the Affinidi Portal:

  1. Go to  Affinidi Portal and click on the Affinidi Iota Framework page.

  2. Click on Create Configuration and set the following fields:

  • Signing 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.

  • Data sharing flow mode: Affinidi Iota Framework provides different modes of enabling consent-drive data-sharing flow on your applications. Select the option on how you want to handle the data-sharing request. More about this mode here.

  • Lifetime of Request Token: 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 Credential 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.

  1. After setting the fields, including the Wallet used for signing and additional options, click Create.
Create Affinidi Iota Framework
  1. After creating the configuration, define the Presentation Definitions to query specific data from the Affinidi Vault. We will define the Presentation Definition from the Presentation Exchange protocol to do this.
Create Presentation Definition
  1. Provide the name of the Presentation Definition and then select from the available templates to pre-populate the editor. You can modify the presentation definition template based on the required data you would like to request from the Affinidi Vault.
Create Presentation Definition Page

Click on the + Add button to create additional presentation definitions for the current configuration. After adding the presentation definitions, click on the Create button.

Each Presentation Definition created will have a corresponding Query ID. The Query ID is used to get the presentation definition required to generate the request token to request data from the Affinidi Vault.

Create a Personal Access Token

After creating the Affinidi Iota Framework configuration and the Presentation Definition to query the data from 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 IotaToken --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 Affinidi Iota Framework.

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"
}

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 and KEY_ID in env file empty.

Try the Data Sharing 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 Receive Credentials page, either the page that uses the WebSocket or the page that uses the Redirect data-sharing flow mode.

On the Receive Credentials page, you will see a dropdown field listing all the available Affinidi Iota Framework configurations created on your project.

Affinidi Iota Framework - Receive Credentials

Select the Affinidi Iota Framework configuration, including the method of opening your Affinidi Vault, whether a browser popup or a new tab if you are using WebSocket mode or select the Redirect URL if you are using Redirect mode. Also, select the Query or the Presentation Definition you defined on your configuration to query the data from the Affinidi Vault.

Once all the options are selected, click on the Share button. This will show the consent screen of the Affinidi Vault, which displays the data being requested and asks if you would like to allow access to this data.

How the Integration Works

After configuring and testing the data-sharing flow through the Affinidi Iota Framework, let’s review the codes and how they work to enable the framework.

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.

  • Iota client to call Affinidi Iota Framework service to load the configurations.

  • Common utility libraries to build the share link and redirect the user to the Affinidi Vault.

Read more about Affinidi TDK here.

Generating a Signed Request Token

The backend implementation of the Affinidi Iota Framework exposes an API endpoint responsible for generating the signed request token used by the front end to request and receive data from Affinidi Vault. It utilises two modules provided by the Affinidi TDK:

Source Path: src/lib/clients/iota.ts

  • Auth Provider package that authenticates and authorises the Personal Access Token (PAT) we configured earlier to call the Iota client to initiate the data-sharing request. The initiated data-sharing request requires the Affinidi Iota Framework Configuration ID and Redirect URL.

  • Iota client to initiate the data-sharing request and parse the signed request token to redirect user to the Affinidi Vault.

export async function initiateDataSharingRequest(
  configurationId: string,
  queryId: string,
  redirectUri: string,
  nonce: string
) {
  const authProvider = getAuthProvider();
  const api = new IotaApi(
    new Configuration({
      apiKey: authProvider.fetchProjectScopedToken.bind(authProvider),
      basePath: `${apiGatewayUrl}/ais`,
    })
  );

  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 };
}

Initialising Data Sharing Request

After setting up the API endpoint to generate the Signed Request Token, in the front end, we shall initialise the page, get the signed request token, and redirect the user to the Affinidi Vault.

In our sample application, the handleRedirectFlowShare is enabled whenever you select an Affinidi Iota Framework configuration from the dropdown list.

You may refer to the configurationsQuery query for the sample code to load the list of configurations from your project using the Iota client of the Affinidi TDK.

Source Path: src/components/iota/RedirectFlowPage.tsx

async function handleRedirectFlowShare(queryId: string) {
    setIsFormDisabled(true);

    const response = await fetch("/api/iota/init-share", {
      method: "POST",
      body: JSON.stringify({
        configurationId: selectedConfigId,
        queryId,
        redirectUri: selectedRedirectUri,
        nonce,
      }),
      headers: {
        "Content-Type": "application/json",
        Accept: "application/json",
      },
    });

    const data = await response.json();

    const toStore = {
      nonce,
      configurationId: selectedConfigId,
      correlationId: data.correlationId,
      transactionId: data.transactionId,
    };

    localStorage.setItem("iotaRedirect", JSON.stringify(toStore));

    const vaultLink = getShareLink(data.jwt, "client_id");
    router.push(vaultLink);
}

After fetching the signed request token, we build the share link to redirect the user to the Affinidi Vault using the VaultUtils class of the Affinidi TDK common package.

function getShareLink(jwt: string, clientId: string) {
    if (typeof window !== "undefined" && window.localStorage) {
      const vaultUrl = window.localStorage.getItem("affinidiVaultUrl");

      if (vaultUrl) {
        return buildShareLinkInternal(vaultUrl, jwt, clientId);
      }
    }

    return VaultUtils.buildShareLink(jwt, clientId);
}

The handleRedirectFlowShare is attached to the Share button and is enabled when selecting a Configuration from the dropdown list.

{selectedQuery && (
    <>
        <h1>Generated nonce: {nonce}</h1>
        <br />

        <Button
        disabled={isFormDisabled}
        onClick={() => handleRedirectFlowShare(selectedQuery)}
        >
        Share
        </Button>
    </>
)}

After building the share link, the user is redirected to Affinidi Vault to query the data and ask for the user’s consent. The response is sent to the callback endpoint and parsed from the backend using the response_code included when the user is redirected back to the specified Redirect URL.

Source Path: src/components/iota/RedirectFlowCallbackPage.tsx

const getIotaResponse = async (params: GetIotaResponseParams) => {
  const response = await fetch("/api/iota/iota-response", {
    method: "POST",
    body: JSON.stringify(params),
    headers: {
      "Content-Type": "application/json",
      Accept: "application/json",
    },
  });
  return await response.json();
};

The Iota Response is fetched from the backend using the response_code sent from the Affinidi Vault.

Source Path: src/lib/clients/iota.ts

export async function fetchIotaVpResponse(
  configurationId: string,
  correlationId: string,
  transactionId: string,
  responseCode: string
) {
  const authProvider = getAuthProvider();
  const api = new IotaApi(
    new Configuration({
      apiKey: authProvider.fetchProjectScopedToken.bind(authProvider),
      basePath: `${apiGatewayUrl}/ais`,
    })
  );

  const iotaVpResponse: FetchIOTAVPResponseOK = await api.fetchIotaVpResponse({
    configurationId,
    correlationId,
    transactionId,
    responseCode,
  });

  const vp = JSON.parse((iotaVpResponse.data as any).vpToken);
  return { vp: vp, nonce: iotaVpResponse.data.nonce };
}

The Verifiable Presentation token (vpToken) is parsed from the Iota Response, including the nonce value, to verify the request and response. The response is a JSON format, like the sample below:

{
  "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": {
      ...
    }
  }
}

The verifiableCredential can have multiple entries depending on the Presentation Definition you defined and the Verifiable Credentials shared by the user from their Affinidi Vault.