Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / Node.js

5 Simple Steps to Integrate Your App with Affinidi Login

18 Jan 2024CPOL6 min read 5K  
Learn to build a secure JavaScript app using Affinidi Login for password-free authentication and enhanced user privacy.

New Ways to do Familiar Things

In 5 simple steps, get started with the Affinidi Trust Network for enhanced User Identity Management and Data Privacy.

What are We Building

A simple JavaScript application where an authenticated user would be able to view the identity token (idToken) issued to the application by the identity provider in this process.

What is Familiar

This application would use OpenID Connect protocol to reach out to the identity provider, much like Social Logins (Login with Google/Facebook/…). The application would receive the idToken from the identity provider, the way OIDC guarantees.

What is New

Image 1

The identity provider for this application would be its USERS, not a third-party system. When a user requests sign-in; they would be empowered to prove who they are, using their own identity without relying on passwords.

This Secure, Efficient, Privacy Preserving NEW way would enable this application to provide a better personal experience to the user; since the application would not just sign-in a user but can also sign them up without any forms.

Before you begin

  1. Set up Affinidi Vault browser extension. This vault would enable users of our application to collect, store and share their identity; their data and to act as their own identity provider
    1. Install the Affinidi Vault Browser Extension via Google Chrome store.

    Affinidi Vault

    1. Click the Affinidi Vault Chrome extension found in the Extensions of the Chrome browser. Click START and provide a valid email address. If you have a backup of your Vault created previously, you can use RESTORE VAULT FROM BACKUP.

    Image 2

    1. Enter the 6-digit verification code sent to the email address you provided in the previous step and click verify.

    Image 3

    1. Secure your Vault by providing a secure passphrase. Use this passphrase to unlock your Vault.

    Image 4

    Finally, you should see on the screen that you have successfully registered to the Affinidi Vault.

    Vault Passphrase

    Remember to keep your passphrase in a secure location since it cannot be retrieved if forgotten, even with your backup. If you forget it, you must reinstall and reconfigure your Vault.

  1. Install Node.js : This is an open source JavaScript runtime environment, much like the runtime that your browser uses to render JavaScript applications.
  2. Optionally, 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.

Node Version

Affinidi CLI requires Node version 18 and above.

  1. Install Affinidi CLI using Node Package Manager (npm).
Bash
npm install -g @affinidi/cli
  1. Verify that the installation is successful.
Bash
affinidi --version

#Lets Build

Step 1 : Set up Your Application

In this step, we would create our package structure and then turn this application into a Node.js application. We will then create the file (index.js) which would represent our entire application. We will also create an environment file to hold all the secrets.

And finally we will install the node packages, which would help us build the required components.

Bash
mkdir hello-world-affinidi-login
cd hello-world-affinidi-login

npm init -y
touch index.js
touch .env

npm install express dotenv passport openid-client express-session

# Why we do need each of these modules
#
# express is a minimalist web framework for Node.js
# dotenv helps loads environment variables from .env, created above
# passport is express-compatible authentication framework for Node.js
# openid-client is Relying-Party implementation for Node.js, which is passport compatible
# express-session is session middleware for express, required by passport

To create a application with authentication support, let’s list down the required structure. We shall build on this as we go.

File Path : index.js

JavaScript
//  1. Import required Libraries
//  2. Create an express application and setup session
//  3. Define application end-points
//  4. Start the http server
//  5. Integrate Authentication

Step 2 : Implement Core Logic for the Application

In this step, we will build the core logic of our application following the requirements that we created above

File Path : index.js

JavaScript
//  1. Import required Libraries
const express = require('express');
const passport = require('passport');
const session = require('express-session');
const { Issuer, Strategy } = require('openid-client');
const http = require("http");
require("dotenv").config();

//  2. Create an express application and setup session
const app = express();
app.use(session({
    secret: 'my-secret',
    resave: false ,
    saveUninitialized: false
}));

// 3. Define application end-points
app.get("/", (req, res) => {
    res.send(" Lets build a Unified Digital Identity <br/><br/><a href='/login'> Affinidi Login</a>");
})

app.get('/login',
    function (req, res, next) {
        next();
    },
);

app.get('/login/callback', (req, res, next) => {})

app.get("/protected", (req, res) => {
    res.header("Content-Type", 'application/json');
    res.end("Protected Content for Authenticated Users");
})

// 4. Start the http server
const httpServer = http.createServer(app)
httpServer.listen(8080, () => {
    console.log(`Hello World - Affinidi Login : Up and running on 8080`)
})

For better housekeeping, let’s first create 3 environment variables

File Path : .env

Bash
issuer= #Verifiable Identifier for the issuer of claims, in this case, Affinidi Login 
client_id=  #Public Identifier for our application, issued by Affinidi Login
client_secret= #Secret known to our application

Step 3 : Integrate Authentication to the Application

In this step, we would create the authentication strategy affinidi-login for our application using openid-client. This strategy would then be provided to the passport middleware.

JavaScript
// 5. Integrate Authentication
// 5a. Discover Affinidi Login - Issuer
Issuer.discover(process.env.issuer).then(function (oidcIssuer) {
  // 5b. Create a RP-client which can initiate an OIDC flow
  var client = new oidcIssuer.Client({
    client_id: process.env.client_id,
    client_secret: process.env.client_secret,
    redirect_uris: ["http://localhost:8080/login/callback"],
    response_types: ['code'],
    token_endpoint_auth_method: 'client_secret_post'
  });

  // 5c. Provide this strategy to the passport middleware
  passport.use(
    'affinidi-login', new Strategy({ client, passReqToCallback: true }, (req, tokenSet, userinfo, done) => {
      req.session.tokenSet = tokenSet;
      req.session.userinfo = userinfo;
      return done(null, tokenSet.claims());
    }));
});

passport.serializeUser(function (user, done) {
  done(null, user);
});
passport.deserializeUser(function (user, done) {
  done(null, user);
});

Step 4 : Create Application Routes to use Authentication

In this step, we will integrate the authentication from last steps into our application routes. From the landing page(/), we will invoke (/login), utilising the configured passport strategy to authenticate users.

Here is what the final index.js would look like

FilePath : index.js

JavaScript
//  1. Import required Libraries
const express = require('express');
const passport = require('passport');
const session = require('express-session');
const { Issuer, Strategy } = require('openid-client');
const http = require("http");
require("dotenv").config();

//  2. Create an express application and setup session
const app = express();
app.use(session({
    secret: 'my-secret',
    resave: false ,
    saveUninitialized: false
}));
app.use(passport.initialize());
app.use(passport.session());

// 3. Define application end-points
app.get("/", (req, res) => {
    res.send(" Lets build a Unified Digital Identity <br/><br/><a href='/login'> Affinidi Login</a>");
})

app.get('/login',
    function (req, res, next) {
        next();
    },
    passport.authenticate('affinidi-login',{scope:'openid'})
);

app.get('/login/callback', (req, res, next) => {
    passport.authenticate('affinidi-login', {successRedirect: '/protected', failureRedirect: '/'})(req,res,next)
})

app.get("/protected", (req, res) => {
    res.header("Content-Type", 'application/json');
    res.end(JSON.stringify(req.user, null, 4));
})

// 4. Start the http server
const httpServer = http.createServer(app)
httpServer.listen(8080, () => {
    console.log(`Hello World - Affinidi Login : Up and running on 8080`)
})

// 5. Integrate Authentication
// 5a. Discover Affinidi Login - Issuer
Issuer.discover(process.env.issuer).then(function (oidcIssuer) {
    // 5b. Create a RP-client which can initiate an OIDC flow
    var client = new oidcIssuer.Client({
      client_id: process.env.client_id,
      client_secret: process.env.client_secret,
      redirect_uris: ["http://localhost:8080/login/callback"],
      response_types: ['code'],
      token_endpoint_auth_method: 'client_secret_post'
    });
  
    // 5c. Provide this strategy to the passport middleware
    passport.use(
      'affinidi-login', new Strategy({ client, passReqToCallback: true }, (req, tokenSet, userinfo, done) => {
        req.session.tokenSet = tokenSet;
        req.session.userinfo = userinfo;
        return done(null, tokenSet.claims());
      }));
  });
  
  passport.serializeUser(function (user, done) {
    done(null, user);
  });
  passport.deserializeUser(function (user, done) {
    done(null, user);
  });

Step 5 : Integrate Affinidi Login and Run Your Application

What we have built so far is an application that can initiate OIDC flow with an OpenIDProvider, authenticate the users and receive an identity token back. This token is then displayed on the (/protected) page.

In order for this application to utiliise Affinidi Login, we need to create a Login Configuration. For this, you can either use Affinidi CLI or Affinidi Portal.

While creating the login-configuration, use redirect-uri : http://localhost:8080/login/callback. You may provide a name of your choice and leave all other options as default

Using Affinidi CLI
  1. Log in to Affinidi CLI by running:
Bash
affinidi start
  1. Once you have successfully logged in, create the Login Configuration by running:
Bash
affinidi login create-config \
--name='Hello World App' \
--redirect-uris='http://localhost:8080/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.

Sample response:

JSON
{
  "ari": "ari:identity:ap-southeast-1:687b8872-a618-dt63-8978-e72ac32daeb1:login_configuration/c4f74d936cd31bde1c1fd3c1050bb76s",
  "projectId": "687b8872-a618-4e52-8978-e72ac32daec2",
  "configurationId": "c4f74d936cd31bde1c1fd3c1050bb62d",
  "name": "My Basic App",
  "auth": {
    "clientId": "<AUTH.CLIENT_ID>",
    "clientSecret": "<AUTH.CLIENT_SECRET>",
    "issuer": "https://<PROJECT_ID>.apse1.login.affinidi.io"
  },
  "redirectUris": [
    "..."
  ],
  "clientMetadata": {
    "name": "My Basic App",
    "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

Image 5

  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.

Image 6

  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.

After using either of these methods, you would receive Client Id, Client Secret and Issuer(auth.issuer) from the response. Replace these values in .env file, that we created in Step 2

File Path : .env

Bash
client_id=cliEnt0101-07d5-xxxx-1111-d2de03f62456
client_secret=mYsEcReTmYsEcReT
issuer=https://<PROJECT_ID>.apse1.login.affinidi.io

Run the application and test the integration at http://localhost:8080

Bash
node index.js

Summary

After the user authentication is complete, the home page (/protected) shows the identity token that was issued for the application usage. This token was generated based on the login configuration we created in the final step above.

Here is the sample identity token. Under the custom node are the claims that are returned to you through user’s consent as zero-party data. You receive this data straight from the users of your applications, not from 3rd party systems.

JSON
{
    "acr": "0",
    "at_hash": "0A1HHZ4xu0Cpv0gLKqnZ_Q",
    "aud": [
        "eed5f015-07d5-1111-893c-d2de03f62456"
    ],
    "auth_time": 1698111980,
    "custom": [
        {
            "type": [
                "VerifiableCredential",
                "Email"
            ]
        },
        {
            "email": "test@gmail.com"
        },
        {
            "did": "did:key:..."
        }
    ],
    "exp": 1698112882,
    "iat": 1698111982,
    "iss": "https://<PROJECT_ID>.apse1.login.affinidi.io",
    "jti": "d96ea1d4-f8d1-aaBB-ccDD-e3d627d1bde9",
    "rat": 1698111949,
    "sid": "26c7c401-xxYY-4cb1-880c-435599ad6b1a",
    "sub": "did:key:..."
}

Bonus

Let’s say you want to know more about your users in order to provide better services without the users having to fill out repeated forms and get more accurate data.

Instead of relying on the B2B integrations that you do today, limited by the data offered by the 3rd party system; let’s see how you can do the same in this new world.

All you need to do is modify the Login Configuration created above (i.e. modify the presentation definition and the idTokenMapping) or refer to this documentation on how to customise the Presentation Definition and ID Token Mapping for your Login requirements.

And ? That’s it, that’s all that you need.

Download the sample JSON file here. This file defines a login configuration where an application requires multiple user’s identity attribute from user’s vault and in a specific defined formal of idToken.

Bash
affinidi login update-config --id=<login-config-id-created-above> --file="profile-pex.json"

Run the application again to see the difference in the custom token that now comes back to this application.

In this simple Hello-World, we created a application, integrated it with Affinidi Login and received zero party data from the users in just 5 simple steps. As a developer, this enables you to build privacy preserving applications which are secure and which respects user’s ownership of their identity. Better yet, we did all this in a way which does not require us to change the way we do things. Keep building.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)