From 86defbc06966c159420ecd92d7d4c9773775304e Mon Sep 17 00:00:00 2001 From: "A.G.J. Cate" Date: Fri, 24 Jan 2025 13:06:40 +0100 Subject: [PATCH 1/4] chore: provided a readme example to use the VDX external identity API to issue credentials with custom user attributes --- packages/oid4vci-issuer-rest-client/README.md | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/packages/oid4vci-issuer-rest-client/README.md b/packages/oid4vci-issuer-rest-client/README.md index f2ff0629c..8d171804d 100644 --- a/packages/oid4vci-issuer-rest-client/README.md +++ b/packages/oid4vci-issuer-rest-client/README.md @@ -51,6 +51,65 @@ const request: IVCIClientCreateOfferUriRequestArgs = { const result: IVCIClientCreateOfferUriResponse = await agent.vciClientCreateOfferUri(request) ``` +### Use the VDX external identity API to retrieve custom user attributes to issue credentials + +```typescript +import { IAgentContext } from '@veramo/core' +import { IOID4VCIClientCreateOfferUriResponse } from '@sphereon/ssi-sdk.oid4vci-issuer-rest-client' +import fetch from 'cross-fetch'; +import jwtDecode from "jwt-decode"; + +const getUserCustomAttributes = async (baseUrl: string, realmId: string, userId: string): Promise | undefined> => { + const url = `${baseUrl}/${realmId}/users/${userId}`; + + // Fetch the custom attributes of a user + return fetch(url) + .then(async (response) => { + if (response.status >= 400) { + return Promise.reject(`Error: Received status code ${response.status}`) + } + + const data = await response.json() + + return data.custom?.attributes + }) + .catch((error) => Promise.reject(`Failed to fetch user attributes. Error: ${error.message}`)) +} + +const parseToken = async (accessToken: string): Promise<{ realmId: string, userId: string }> => { + // Decode the access token + const decoded = jwtDecode(accessToken) + + // Extract user ID from the 'sub' claim + const userId = decoded.sub + + // Extract realm ID from the 'iss' claim (e.g., "https://example.com/auth/realms/my-realm") + const realmId = decoded.iss.split('/').pop() + + return { realmId, userId } +} + +const createCredentialOfferUri = async (baseUrl: string, accessToken: string, context: IAgentContext): Promise => { + // Parse the access token to get the realm id and user id + const parsedToken = await parseToken(accessToken) + // Retrieve the custom attributes of a user to be used as credential input + const credentialDataSupplierInput = await getUserCustomAttributes(baseUrl, parsedToken.realmId, parsedToken.userId) + + // Create credential offer uri with credential input + return context.agent.oid4vciClientCreateOfferUri({ credentialDataSupplierInput: { + salutation: credentialDataSupplierInput.salutation, + firstName: credentialDataSupplierInput.firstName, + lastName: credentialDataSupplierInput.lastName, + phoneNumber: credentialDataSupplierInput.phoneNumber, + employeeIdNumber: credentialDataSupplierInput.employeeIdNumber, + emailAddress: credentialDataSupplierInput.emailAddress, + jobTitle: credentialDataSupplierInput.jobTitle, + pcc: credentialDataSupplierInput.pcc, + iataCode: credentialDataSupplierInput.iataCode, + }}) +} +``` + ### Installation ```shell From cd9bda9cf1c6a38dc31a9aeebe5372d7940a17bd Mon Sep 17 00:00:00 2001 From: "A.G.J. Cate" Date: Fri, 24 Jan 2025 13:11:16 +0100 Subject: [PATCH 2/4] chore: update example function names --- packages/oid4vci-issuer-rest-client/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/oid4vci-issuer-rest-client/README.md b/packages/oid4vci-issuer-rest-client/README.md index 8d171804d..6ef2077d5 100644 --- a/packages/oid4vci-issuer-rest-client/README.md +++ b/packages/oid4vci-issuer-rest-client/README.md @@ -59,7 +59,7 @@ import { IOID4VCIClientCreateOfferUriResponse } from '@sphereon/ssi-sdk.oid4vci- import fetch from 'cross-fetch'; import jwtDecode from "jwt-decode"; -const getUserCustomAttributes = async (baseUrl: string, realmId: string, userId: string): Promise | undefined> => { +const getUserCustomAttributesFromVDX = async (baseUrl: string, realmId: string, userId: string): Promise | undefined> => { const url = `${baseUrl}/${realmId}/users/${userId}`; // Fetch the custom attributes of a user @@ -76,7 +76,7 @@ const getUserCustomAttributes = async (baseUrl: string, realmId: string, userId: .catch((error) => Promise.reject(`Failed to fetch user attributes. Error: ${error.message}`)) } -const parseToken = async (accessToken: string): Promise<{ realmId: string, userId: string }> => { +const parseKeycloakAccessToken = async (accessToken: string): Promise<{ realmId: string, userId: string }> => { // Decode the access token const decoded = jwtDecode(accessToken) From f01de0dc00be3448c404f4904689fa86e7465188 Mon Sep 17 00:00:00 2001 From: "A.G.J. Cate" Date: Fri, 24 Jan 2025 13:14:29 +0100 Subject: [PATCH 3/4] chore: update example function names --- packages/oid4vci-issuer-rest-client/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/oid4vci-issuer-rest-client/README.md b/packages/oid4vci-issuer-rest-client/README.md index 6ef2077d5..5b3bb6bd8 100644 --- a/packages/oid4vci-issuer-rest-client/README.md +++ b/packages/oid4vci-issuer-rest-client/README.md @@ -91,9 +91,9 @@ const parseKeycloakAccessToken = async (accessToken: string): Promise<{ realmId: const createCredentialOfferUri = async (baseUrl: string, accessToken: string, context: IAgentContext): Promise => { // Parse the access token to get the realm id and user id - const parsedToken = await parseToken(accessToken) + const parsedToken = await parseKeycloakAccessToken(accessToken) // Retrieve the custom attributes of a user to be used as credential input - const credentialDataSupplierInput = await getUserCustomAttributes(baseUrl, parsedToken.realmId, parsedToken.userId) + const credentialDataSupplierInput = await getUserCustomAttributesFromVDX(baseUrl, parsedToken.realmId, parsedToken.userId) // Create credential offer uri with credential input return context.agent.oid4vciClientCreateOfferUri({ credentialDataSupplierInput: { From 408b2d41cf73c85e246b85a76119cf9526d256fc Mon Sep 17 00:00:00 2001 From: "A.G.J. Cate" Date: Fri, 24 Jan 2025 13:16:21 +0100 Subject: [PATCH 4/4] chore: single quotes --- packages/oid4vci-issuer-rest-client/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/oid4vci-issuer-rest-client/README.md b/packages/oid4vci-issuer-rest-client/README.md index 5b3bb6bd8..909fb6009 100644 --- a/packages/oid4vci-issuer-rest-client/README.md +++ b/packages/oid4vci-issuer-rest-client/README.md @@ -57,7 +57,7 @@ const result: IVCIClientCreateOfferUriResponse = await agent.vciClientCreateOffe import { IAgentContext } from '@veramo/core' import { IOID4VCIClientCreateOfferUriResponse } from '@sphereon/ssi-sdk.oid4vci-issuer-rest-client' import fetch from 'cross-fetch'; -import jwtDecode from "jwt-decode"; +import jwtDecode from 'jwt-decode'; const getUserCustomAttributesFromVDX = async (baseUrl: string, realmId: string, userId: string): Promise | undefined> => { const url = `${baseUrl}/${realmId}/users/${userId}`;