5 Simple Steps to Integrate Your App with Affinidi Login
New Ways to do Familiar Things
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
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
- Set up Affinidi Vault account. This Affinidi Vault would enable users of our application to collect, store and share their identity; their data and to act as their own identity provider
Install NodeJS : This is an open source Javascript runtime environment, much like the runtime that your browser uses to render Javascript applications.
Optionally, install the Affinidi CLI. Follow the guide below if it hasn’t been installed.
#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.
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
// 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
// 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
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.
// 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
// 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
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
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
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.
{
"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 Affinidi Vault and in a specfic defined formal of idToken.
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.
Glad to hear it! Please tell us how we can improve more.
Sorry to hear that. Please tell us how we can improve.
Thank you for sharing your feedback so we can improve your experience.