Affinidi Login with Auth0

This guide explains how to integrate Affinidi Login with Auth0.

Affinidi Login can be integrated with any Identity Providers (IDPs) supporting custom social login mechanisms and OIDC flow.

This lab uses NextJS and NextAuth.js as the framework to implement Affinidi Login.

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
  1. Set up an Affinidi Vault account using the web app or install the mobile app .

The same installation steps for mobile app.

  1. Click on Start if you are creating a new account, or click on Restore from Backup if you have an existing backup of your Affinidi Vault.

Use this guide to learn how to migrate your existing Affinidi Vault account.

Affinidi Vault Setup
  1. Secure your Vault by providing a passphrase. Use this passphrase to unlock your Vault.
Affinidi Vault Passphrase
  1. Provide your Email Address to verify with OTP.
Affinidi Vault Email Verification

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

  1. Create an Auth0 tenant or sign-in to your existing tenant account where you want to integrate Affinidi Login

  2. Create a Regular Web Application in Auth0 and setup the necessary fields based on your application’s structure (e.g Allowed Callback URLs)

  • In this sample application, set the following in the Regular Web Application:
    • Allowed Callback URLs to http://localhost:3000/api/auth/callback/auth0
    • Allowed Logout URLs and Allowed Web Origins to http://localhost:3000
  1. Optionally, install the Affinidi CLI. Follow the guide below if you haven’t installed yet.
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

Download Application

You can clone this sample application using Next.js framework and explore integrating Affinidi Login with Auth0 as the authorisation server and Affinidi Vault as the Identity Provider to provide a passwordless login experience.

Running the Application

  1. Suppose this is your first time downloading the sample application. Run the following command to install the dependencies.
npm install
  1. Create the .env file in the sample application by running the following command.
cp .env.example .env

If you open the sample application in a Code Editor, duplicate the .env.example and rename it .env.

  1. After installing the dependencies, run the application by running the command.
npm run dev

Running the application for the first time without configuring the .env file will throw an exception error. We will configure the required environment variables in the following steps of this guide.

You may refer to the included README.md file for more details.

Create Login Configuration

To create a Login Configuration, you can either use Affinidi CLI or  Affinidi Portal.

Expand the section below for your preferred method:

Name: My Auth0 config

Redirect URIs: https://{auth0-domain}/login/callback

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='My Auth0 config' \
--redirect-uris='https://{auth0-domain}/login/callback'
  • --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. Replace auth0-domain with the domain generated by Auth0 after creating the application.

Sample response:

{
  "ari": "ari:identity:ap-southeast-1:687b8872-a618-dt63-8978-e72ac32daeb1:login_configuration/c4f74d936cd31bde1c1fd3c1050bb76s",
  "projectId": "687b8872-a618-4e52-8978-e72ac32daec2",
  "configurationId": "c4f74d936cd31bde1c1fd3c1050bb62d",
  "name": "...",
  "auth": {
    "clientId": "<AUTH.CLIENT_ID>",
    "clientSecret": "<AUTH.CLIENT_SECRET>",
    "issuer": "https://<PROJECT_ID>.apse1.login.affinidi.io"
  },
  "redirectUris": [
    "..."
  ],
  "clientMetadata": {
    "name": "Login Config Name",
    "logo": "https://login.affinidi.com/default-client-logo.svg",
    "origin": "https://example.com"
  },
  "creationDate": "2023-08-11T06:26:37Z",
  "tokenEndpointAuthMethod": "client_secret_post"
}

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.

Configure Auth0 Social Connection with Affinidi Login

We create a Social Connection in Auth0, integrating Affinidi Login as the identity provider for authenticating users during the login flow. Additionally, we configure Auth0 to parse the idToken provided by Affinidi Login once users verify their identity through Affinidi Vault.

Auth0 Set up Social Connection
  1. In Auth0 Dashboard, go to Authentication > Social and click on Create Connection

  2. Select Create Custom and set the following fields in the Auth0 with the values below:

  • Authorization URL: auth.issuer/oauth2/auth
  • Token URL: auth.issuer/oauth2/token
  • Scope: openid offline_access
  • Client ID: set the value from the auth.clientId of the Login Configuration
  • Client Secret: set the value from the auth.clientSecret of the Login Configuration

auth.issuer is the value from Login Configuration.

  1. Copy the code below and paste it in the Fetch User Profile Script of the Social Connection

This script is called after the user consented to share their email address from their Affinidi Vault account. Auth0 executes this script and extracts the user_id, email, and the custom fields where additional data are populated like user address from the idToken provided by Affinidi Login after successful login: s


function fetchUserProfile(accessToken, context, callback) {
  const idToken = JSON.parse(
    Buffer.from(context.id_token.split(".")[1], "base64").toString()
  );

  const profile = {
    user_id: idToken.sub,
    email: idToken.custom.find((c) => c.email).email,
    profile: idToken.custom,
  };

  callback(null, profile, context);
}
  1. Once you create the Social Connection, enable the application to use it in the Applications view.

  2. You can now enable your Auth0 in your application to use the custom connection. This enables users to login using their Affinidi Vault.

Set up the Sample Application

Affinidi Login Auth0 Sample

In this guide, learn how to integrate Affinidi Login using NextJS and NextAuth.js with Auth0 as the authorisation server into your application.

Auth0 is configured to connect to Affinidi Login as the Identity provider to authenticate user.

Configure .env file

Set the environment variables based on the auth credentials generated by Auth0 when you created the Application:

PROVIDER_CLIENT_ID="<AUTH0_APP_CLIENT_ID>"
PROVIDER_CLIENT_SECRET="<AUTH0_APP_CLIENT_SECRET>"
PROVIDER_ISSUER="<AUTH0_APP_CLIENT_DOMAIN>"
  • AUTH0_APP_CLIENT_ID: Client ID generated by Auth0 after creating an Application.
  • AUTH0_APP_CLIENT_SECRET: Client Secret generated by Auth0 after creating an Application.
  • AUTH0_APP_CLIENT_DOMAIN: Domain generated by Auth0 after creating an Application.

Configure NextAuth for Affinidi Login

Using the NextAuth.js library, configure the nextauth file to enable Affinidi Login as a login provider and set up the JWT and Session from the idToken sent by the Affinidi Login after the user successfully authenticates.

Add Auth0 Login as a Login Provider

In the code below, we are adding Auth0 as one of the Login Provider for NextAuth.js.

File path: src/lib/auth/auth-provider.ts

import Auth0Provider from "next-auth/providers/auth0";
import {
  providerClientId,
  providerClientSecret,
  providerIssuer,
} from "src/lib/secrets";
import { socialConnectorName } from "src/lib/variables";

export const PROVIDER_ATTRIBUTES_KEY = "profile";

export const provider = Auth0Provider({
  clientId: providerClientId,
  clientSecret: providerClientSecret,
  issuer: providerIssuer,
  authorization: {
    params: {
      scope: "openid offline_access email profile",
      prompt: "login",
      ...(socialConnectorName && { connection: socialConnectorName }),
    },
  },
});
Generate JWT and Session from the idToken

In the code below, the JWT and the session are generated from the idToken received from Affinidi Login following successful user authentication.

File path: src/lib/auth/auth-options.ts


async jwt({ token, account, profile }) {
  const profileItems = (profile as any)?.[PROVIDER_ATTRIBUTES_KEY];
  if (profile && profileItems) {
    let userDID: string;
    let user: UserInfo = {};
    userDID = profileItems.find(
      (item: any) => typeof item.did === "string"
    )?.did;
    user.email = profileItems.find(
      (item: any) => typeof item.email === "string"
    )?.email;
    user.country = profileItems.find(
      (item: any) => typeof item.address === "object"
    )?.address?.country;
    token = {
      ...token,
      user,
      ...(userDID && { userId: userDID }),
    };
  }

  if (account) {
    token = {
      ...token,
      ...(account?.access_token && { accessToken: account.access_token }),
      ...(account?.id_token && { idToken: account.id_token }),
    };
  }

  return token;
},
// session is persisted as an HttpOnly cookie
async session({ session, token }) {
  return {
    ...session,
    ...(token.user && { user: { ...session.user, ...token.user } }),
    ...(token.accessToken && { accessToken: token.accessToken }),
    ...(token.idToken && { idToken: token.idToken }),
    ...(token.userId && { userId: token.userId }),
  };
},

The above files are then declared in the NextAuth file to register the login provider and the functions to handle the idToken response from Affinidi Login.

File path: src/pages/api/auth/[...nextauth].ts

import { authOptions } from "src/lib/auth/auth-options";
import NextAuth from "next-auth";

export default NextAuth(authOptions);

Additionally, you will notice in line 31 that we are parsing the country field. This optional process requires updating the Presentation Definition and ID Token Mapping of the Login Configuration to request the user profile from the Vault.

user.country = profileItems.find(
      (item: any) => typeof item.address === "object"
    )?.address?.country;

You can refer to this guide to learn how to update the Presentation Definition and ID Token Mapping.

Enable Affinidi Login

In the code below, we are enabling Affinidi Login into the Login page of the sample app and referencing the NextAuth provider configured earlier.

File path: src/lib/auth/client-login.ts

import { signIn } from "next-auth/react";
import { hostUrl } from "src/lib/variables";

export async function clientLogin() {
  await signIn("affinidi", { callbackUrl: hostUrl });
}

We include the above file in the SignIn page of the sample app and attach the event in the Login button to initiate the authentication flow.

<Button variant="primary" onClick={clientLogin}>
  <Image src={LogoAffinidi} alt="logo affinidi" />
  Affinidi Login
</Button>

Summary

sequenceDiagram
    actor User
    participant Website
    participant Auth0
    participant Affinidi Login
    participant Affinidi Vault
    participant Affinidi Verifier

    User->>Website: My Login
    Website->>Auth0: trigger OIDC flow
    Note over Website, Auth0: Auth0 Application credentials
    Auth0->>Affinidi Login: Authenticate user
    Note over Auth0, Affinidi Login:  login_challenge
    Affinidi Login->>Affinidi Vault: Verify user identity
    Note over Affinidi Login, Affinidi Vault:  presentationDefinition
    Affinidi Vault->>User: Request user confirmation to share Email VC
    User->>Affinidi Vault: User confirmed consent to share Email VC
    Affinidi Vault->>Affinidi Vault: Generate VP Token from VC
    Affinidi Vault->>Affinidi Login: Send Email VP Token
    Affinidi Login->>Affinidi Verifier: Validate VP Token
    Note over Affinidi Login, Affinidi Verifier:  vp_token, presentation_submission, presentation_definition
    Affinidi Login->>Affinidi Login: Generate idToken
    Affinidi Login->>Auth0: Send generated idToken from VP
    Auth0->>Website: Send JSON Web Token
    Website->>User: Provide access to the user

In the sample application, we integrated Auth0 as the OIDC provider and subsequently configured Auth0 to integrate with Affinidi Login as the identity provider. Affinidi Login interacts with the Affinidi Vault to verify the user’s identity and provide the VP Token. This token is then consumed by Auth0 as part of the idToken. Based on the idToken sent by Affinidi Login, Auth0 generates the JWT to confirm the user’s authentication.