Sample App with WebSocket Implementation

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

This guide provides details on how to enable a consent-driven data sharing flow using the WebSocket mode of the Affinidi Iota Framework. Using the WebSocket mode, the request and response is processed using the WebSocket channel that is created when the Iota Session is initialised. Applications using the WebSocket mode is required to implement the Affinidi Login to parse the user’s Decentralised Identifier (DID) to generate the signed request token.

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 Login

The Affinidi Iota Framework requires the Decentralised Identifier (DID) of the user to generate the Iota Credentials to sign the Request Token and allow only the user who has proven ownership of the DID to access the Iota Credentials to protect users from phishing and forwarding attacks; we will implement Affinidi Login as an authentication and authorisation method.

Create Login Configuration

Name: Affinidi Sample App

Redirect URIs: http://localhost:3000/api/auth/callback/affinidi

Using Affinidi CLI
  1. Log in to Affinidi CLI by running:
affinidi start
  1. Once you have successfully logged in, create the Login Configuration by running:
affinidi login create-config --name='Affinidi Sample App' --redirect-uris='http://localhost:3000/api/auth/callback/affinidi'
  • --name is what you want your login configuration to be called.
  • --redirect-uris is the URL on your application where the user gets redirected after the successful authentication.

Learn more on how to manage your Login Configurations using Affinidi CLI.

Using Affinidi Portal
Create new Login Configuratioin
  1. Go to  Affinidi Login under the Services section.

  2. Click on the Create Login Configuration and provide the required details.

  • Name is the string that describes your login configuration.
  • Redirect URIs is the URL on your application where the user gets redirected after the successful authentication.
  1. Click on create and confirm if all the details are correct.
Login Configuratation new client
  1. After confirming the details, another popup shows the Client ID and Client Secret for your Login Configuration. Copy the generated Client Credentials and use them to integrate with Affinidi Login.

  2. After copying the Client ID and Client Secret and closing the popup, you are redirected back to the Affinidi Login page.

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.

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.

Integrate Affinidi Iota Framework

After successfully setting up the Affinidi Login, let’s configure the integration to enable data-sharing flow with the Affinidi Iota Framework.

Before starting the integration, create the Affinidi Iota Framework Configuration and define the Presentation Definition for querying data from Affinidi Vault. Follow this guide for instructions on how to do so.

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 Core library to generate the Iota Credentials for signing the Request Token.

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

  • Iota Browser library to initialise the Iota Session and prepare the Request Token to request and receive data from the Affinidi Vault.

Read more about Affinidi TDK here.

Generating Iota Credentials for Signing Request Token

The backend implementation of the Affinidi Iota Framework exposes an API endpoint responsible for generating the Iota Credentials for signing the Request Token used by the frontend to request and receive data from Affinidi Vault. It utilises two modules provided by the Affinidi TDK:

Source Path: src/pages/api/iota/start.ts

  • Auth Provider package that authenticates and authorises the Personal Access Token (PAT) we configured earlier and generates the Iota Token. The Iota Token generation requires the Affinidi Iota Framework Configuration ID and the logged-in user’s DID provided by Affinidi Login.

  • Iota Core package that generates the Iota Credentials required to sign the request token to request data from Affinidi Vault. It takes the JWT from the Iota Token generated by the Auth Provider as a parameter.

try {
    const session = await getServerSession(req, res, authOptions);
    if (!session) {
      res.status(401).json({ message: "You must be logged in." });
      return;
    }
    const { iotaConfigurationId } = iotaStartSchema.parse(req.query);

    const authProvider = getAuthProvider();
    const iotaToken = authProvider.createIotaToken(
      iotaConfigurationId,
      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);
}

Initialising Affinidi Iota Framework Session

After setting up the API endpoint to generate the Iota Credentials, in the front end, we shall initialise the IotaSession using the Iota Browser package of the Affinidi TDK.

In our sample application, the IotaSession is initialised 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/IotaClientPage.tsx

async function getIotaCredentials(configurationId: string) {
    const response = await fetch(
      "/api/iota/start?" +
      new URLSearchParams({
        iotaConfigurationId: configurationId,
      }),
      {
        method: "GET",
      },
    );
    return (await response.json()) as IotaCredentials;
}

The IotaSession is initialised through the useQuery hook that gets triggered when selecting a Configuration from the dropdown list.

const iotaSessionQuery = useQuery({
    queryKey: ["iotaSession", selectedConfigId],
    queryFn: async ({ queryKey }) => {
      const credentials = await getIotaCredentials(queryKey[1]);
      const iotaSession = new Session({ credentials });
      await iotaSession.initialize();
      return iotaSession;
    },
    enabled: !!selectedConfigId,
});

Before initialising the Iota Session, the getIotaCredentials function was called to get the Iota Credentials from the API endpoint we created earlier. The Iota Credentials are passed as a parameter to the Iota Session to initialise it.

The IotaSession opens a WebSocket connection that listens to the callback events.

Handling the Data Sharing Request

To trigger the data-sharing flow in the sample application, we have created an async function called handleTDKShare that prepares and signs the request token using the initialised IotaSession. It takes the ID of the selected Query, which is the Presentation Definition used to query data from the Affinidi Vault.

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

async function handleTDKShare(queryId: string) {
    if (!iotaSessionQuery.data) {
      throw new Error("Iota session not initialized");
    }
    try {
      setIsFormDisabled(true);
      const request = await iotaSessionQuery.data.prepareRequest({ queryId });
      setIsFormDisabled(false);
      addNewDataRequest(request);
      request.openVault({ mode: openMode });
      const response = await request.getResponse();
      updateDataRequestWithResponse(response);
    } catch (error) {
      if (error instanceof IotaError) {
        updateDataRequestWithError(error);
        console.log(error.code);
      }
    }
}

The handleTDKShare function is attached to the rendered button as an onClick event.

{iotaSessionQuery.isSuccess && selectedQuery && (
  <Button
    disabled={isFormDisabled}
    onClick={() => handleTDKShare(selectedQuery)}
  >
    Share
  </Button>
)}

After getting the signed request token, the Affinidi Vault opens to query the data and ask for the user’s consent. The response is returned to the WebSocket, opened by the IotaSession and parsed by the request instance. The request.getResponse() function returns the response in JSON format, like the sample below.

{
  "correlationId": "b099967b-8736-44db-93fa-a741e8016848",
  "verifiablePresentation": {
    "@context": [
      "https://www.w3.org/2018/credentials/v1"
    ],
    "id": "claimId:gpf1UbjaUwwFtgKUbu_Yj",
    "holder": {
      "id": "did:key:zQ3shPh4UnmNgNza3TB2KkhKDrcEVVnntyjdNWYqDg8jKo6Gj"
    },
    "proof": {
      ...
    },
    "type": [
      "VerifiablePresentation"
    ],
    "verifiableCredential": [
      {
        "@context": [
          "https://www.w3.org/2018/credentials/v1",
          "https://schema.affinidi.com/EmailV1-0.jsonld"
        ],
        "credentialSchema": {
          "id": "https://schema.affinidi.com/EmailV1-0.json",
          "type": "JsonSchemaValidator2018"
        },
        "credentialSubject": {
          "email": "info@affinidi.com"
        },
        "holder": {
          "id": "did:key:zQ3shPh4UnmNgNza3TB2KkhKDrcEVVnntyjdNWYqDg8jKo6Gj"
        },
        "id": "claimId:31de43221eeb658e",
        "issuanceDate": "2024-07-03T05:05:56.702Z",
        "issuer": "did:key:zQ3shtMGCU89kb2RMknNZcYGUcHW8P6Cq3CoQyvoDs7Qqh33N",
        "proof": {
          ...
        },
        "type": [
          "VerifiableCredential",
          "Email"
        ]
      }
    ]
  },
  "presentationSubmission": {
    "id": "NT080x7PwL3hG2MFxtCor",
    "definition_id": "token_with_email_vc",
    "descriptor_map": [
      {
        "id": "email_vc",
        "path": "$.verifiableCredential[0]",
        "format": "ldp_vc"
      }
    ]
  }
}

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.