Sample App with Redirect Implementation

Use this guide to set up and run a Next.js sample application that integrates with Affinidi Iota Framework using Redirect mode. This helps you get started quickly.

Redirect mode enables a consent-driven data-sharing flow without requiring Affinidi Login. It uses a Redirect URL to generate a signed request token and initiate data sharing.

Before you begin

Ensure you have:

  1. An Affinidi Vault account. Follow this guide if you haven’t set it up.

    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.

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

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

  2. Affinidi CLI installed. Follow the guide below if not nstalled.

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. Git installed on your machine. Follow this guide on how to install Git.

  2. Node.js installed on your machine - NodeJS.

Download the Application

Download the sample code as a ZIP from the GitHub repo or generate it using Affinidi CLI:

Download the sample code as a ZIP from the GitHub Repo or generate it using Affinidi CLI:

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 generates the code sample in the affinidi-sample-app directory.

Install App Dependencies

Navigate to the affinidi-sample-app directory and install dependencies:

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
  • define a Presentation Definition to query the user data from the Affinidi Vault.

To learn more about Affinidi Iota Framework Configuration and a guide on creating it, visit this page.

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

The PAT includes:

  • Private/public keys (protected with a passphrase)
  • A defined policy granting permissions to call Affinidi Iota Framework

Copy the output and we will configure it later in the sample app’s environment variables.

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 setting up PAT and running the app:

  1. Go to http://localhost:3000.
  2. Click Receive Credentials.
Affinidi Iota Framework - Receive Credentials
  1. On the Receive Credentials page, you will see a dropdown field listing all the available Affinidi Iota Framework configurations created on your project. Select:
  • The Affinidi Iota Framework configuration.
  • The Redirect URL for Redirect mode.
  • The Presentation Definition you created to query data.
  1. Click Share. 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

The sample app uses Affinidi TDK modules, let’s review these:

  • Auth Provider: Authenticates and authorises the PAT.
  • Iota client: Calls Affinidi Iota Framework service to load configurations.
  • Common: Builds the share link and redirects the user to Affinidi Vault.

Read more about Affinidi TDK here.

Generating a Signed Request Token

The backend exposes an API endpoint that generates the signed request token. This token is used by the frontend to request and receive data from Affinidi Vault.

It uses two modules from Affinidi TDK:

  • Auth Provider: Authenticates and authorises the Personal Access Token (PAT) configured earlier. Calls the Iota client to initiate the data-sharing request. The request requires:

  • Affinidi Iota Framework Configuration ID

  • Redirect URL

  • Iota client: Initiates the data-sharing request and parses the signed request token. This token is used to redirect the user to Affinidi Vault.

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

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:

  • Initialise the page in the frontend.
  • Fetch the signed request token.
  • Redirect the user to Affinidi Vault.

In the sample app:

  • The handleRedirectFlowShare function runs when you select an Affinidi Iota Framework configuration from the dropdown.

Refer to the configurationsQuery query or sample code to load configurations 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:

  • Build the share link using VaultUtils from Affinidi TDK’s common package.
  • Attach handleRedirectFlowShare to the Share button.

It activates when a configuration is selected.

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> </> )}

The user is then redirected to Affinidi Vault:

  • The consent screen displays requested data.
  • The user can approve or deny access.

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.