Presentation Definition and ID Token Mapping

Learn more about Presentation Definition and how to modify the query to provide a better experience to the users.

Presentation Definition is an implementation of the Presentation Exchange Protocol developed and published by Decentralized Identity Foundation (DIF). Presentation Definition is a JSON formatted query that defines the data required from the Affinidi Vault user to prove their identity during the authentication flow.

Affinidi Login has a default Presentation Definition that requests Email Verifiable Credentials stored on the user’s Affinidi Vault, generated after successful registration.


{
  "id": "vp_token_with_email_vc",
  "input_descriptors": [
    {
      "id": "email_vc",
      "name": "Email VC",
      "purpose": "Check if data contains necessary fields",
      "constraints": {
        "fields": [
          {
            "path": [
              "$.type"
            ],
            "purpose": "Check if VC type is correct",
            "filter": {
              "type": "array",
              "contains": {
                "type": "string",
                "pattern": "Email"
              }
            }
          },
          {
            "path": [
              "$.credentialSubject.email"
            ],
            "purpose": "Check if VC contains email field",
            "filter": {
              "type": "string"
            }
          },
          {
            "path": [
              "$.issuer"
            ],
            "purpose": "Check if VC Issuer is Trusted",
            "filter": {
              "type": "string",
              "pattern": "^did:key:zQ3shtMGCU89kb2RMknNZcYGUcHW8P6Cq3CoQyvoDs7Qqh33N"
            }
          }
        ]
      }
    }
  ]
}

Defining Presentation Definition

To define the presentation definition, you have to be familiar with Presentation Exchange Query (PEX Query) structure.

Refer to this documentation to know what available data you can request from the Affinidi Vault and define it in the Presentation Definition.

For example, suppose you want to request more details from the end-user, like the address. In that case, you must define the following structure in your presentation definition.

{
    "id": "vp_combined_email_user_profile_combined",
    "submission_requirements": [
      {
        "rule": "pick",
        "min": 1,
        "from": "A"
      }
    ],
    "input_descriptors": [
      {
        "id": "email_vc",
        "name": "Email VC",
        "purpose": "Check if data contains necessary fields",
        "group": ["A"],
        "constraints": {
          "fields": [
            {
              "path": [
                "$.type"
              ],
              "purpose": "Check if VC type is correct",
              "filter": {
                "type": "array",
                "contains": {
                  "type": "string",
                  "pattern": "^Email$"
                }
              }
            },
            {
              "path": [
                "$.credentialSubject.email"
              ],
              "purpose": "Check if VC contains email field",
              "filter": {
                "type": "string"
              }
            },
            {
              "path": [
                "$.issuer"
              ],
              "purpose": "Check if VC Issuer is Trusted",
              "filter": {
                "type": "string",
                "pattern": "^did:key:zQ3shtMGCU89kb2RMknNZcYGUcHW8P6Cq3CoQyvoDs7Qqh33N"
              }
            }
          ]
        }
      },
      {
        "id": "profile_vc",
        "name": "Country VC",
        "purpose": "Check if data contains necessary fields",
        "group": ["A"],
        "constraints": {
          "fields": [
            {
              "path": [
                "$.type"
              ],
              "purpose": "Check if VC type is correct",
              "filter": {
                "type": "array",
                "contains": {
                  "type": "string",
                  "pattern": "^HITCountry$"
                }
              }
            },
            {
              "path": ["$.credentialSubject.country"]
            }
          ]
        }
      }
    ]
}

In the above structure, we have added a new block based on the default presentation definition to be able to request additional data from the Affinidi Vault; in this case, we ask for the address information of the user.

Submission Requirements

The presentation definition may contain submission requirements when requesting additional data from the users, and you may specify the combination of input_descriptors that the user needs to submit to comply with the defined presentation.

"submission_requirements": [
    {
        "rule": "pick",
        "min": 1,
        "from": "A"
    }
]

Let’s go through the submission requirements that we defined on the above example:

  • rule: we are using the pick method to request a single input from the input_descriptors in the definition.
  • min: we want to pick at least 1 input_descriptors from the defined group.
  • from: from which group in the input_descriptors do we want to pick.

You can read more about the submission requirements through this link.

Input Descriptors

Input Descriptors are where we define the data requirements that the user must satisfy to authenticate on your application successfully. It contains the Verifiable Credentials (VCs) that must be available in the Affinidi Vault and the fields that must exist on each VCs.

{
  "id": "profile_vc",
  "name": "profile VC type",
  "purpose": "Check if VC type is correct",
  "group": ["A"],
  "constraints": {
    "fields": [
      {
        "path": [
          "$.type"
        ],
        "purpose": "Check if VC type is correct",
        "filter": {
          "type": "array",
          "contains": {
            "type": "string",
            "pattern": "^HITCountry$"
          }
        }
      },
      {
        "path": ["$.credentialSubject.country"]
      }
    ]
  }
}

From the default presentation definition, we have added additional 2 input descriptors:

  • VC Type: In this input descriptor, we are requesting the Affinidi Vault to provide the HITCountry VC. Please note that in this input descriptor, we are describing from which field the schema definition used can be found from the HITCountry VC: $.credentialSubject.country.

  • Address field: In this input descriptor, we request the Affinidi Vault to provide the Address information of the user that can be found on the specified path from the User Profile VC: $.credentialSubject.country.

Suppose both descriptors are not present on their specific paths, and the condition defined in the pattern field is not satisfied. In that case, the Affinidi Login flow does not proceed and displays an error that the VC is unavailable to the end user.

Additionally, since we are requesting multiple data points from the user, we have to define the group from which the user must comply based on submission requirements.

Mapping ID Token from Presentation Definition

Suppose you have modified the default presentation definition and requested additional data from the user’s Affinidi Vault. In that case, you must change the idTokenMapping of the Login Configuration to include the other data from the VP Token generated by the Affinidi Vault to the ID Token sent to the application after successful authentication.

Below is the default ID Token Mapping defined in the Login Configuration:

[
  {
    "sourceField": "$.type",
    "idTokenClaim": "$.custom[0].type",
    "inputDescriptorId": "email_vc"
  },
  {
    "sourceField": "$.credentialSubject.email",
    "idTokenClaim": "$.custom[1].email",
    "inputDescriptorId": "email_vc"
  }
]

It is an array of JSON objects that defines the source and destination field used to generate the ID Token for the application.

  • sourceField: is the field path defined in the Verifiable Presentation generated by the Affinidi Vault. This is what is defined in the path property of the presentation definition.

  • idTokenClaim: is the field representation of the sourceField in the ID Token that is generated by the Affinidi Login service and sent back to the application. All these fields are defined under the custom property of the ID Token or define a top-level claim. Check this guide to learn more about customising ID Token claims.

  • inputDescriptorId: is the ID of the inputDescriptor from the Presentation Definition where the field value will be parsed.

In the above example, we have requested the address data of the user. To parse and include this data in the ID Token, we have to modify the ID Token Mapping:

[
  {
    "sourceField": "$.type",
    "idTokenClaim": "$.custom[0].type",
    "inputDescriptorId": "email_vc"
  },
  {
    "sourceField": "$.credentialSubject.email",
    "idTokenClaim": "$.custom[1].email",
    "inputDescriptorId": "email_vc"
  },
  {
    "sourceField": "$.credentialSubject.country",
    "idTokenClaim": "$.custom[2].country",
    "inputDescriptorId": "profile_vc"
  }
]

We have added mapping whereby the sourceField is based on the path defined in the presentation definition, and the idTokenClaim field name to use is the country.

Presentation Definition and ID Token Mapping:

For reference, here is the full Presentation Definition and ID Token Mapping Definition based on our example above:

"presentationDefinition": {
    "id": "vp_combined_email_user_profile_combined",
    "submission_requirements": [
      {
        "rule": "pick",
        "min": 1,
        "from": "A"
      }
    ],
    "input_descriptors": [
      {
        "id": "email_vc",
        "name": "Email VC",
        "purpose": "Check if data contains necessary fields",
        "group": ["A"],
        "constraints": {
          "fields": [
            {
              "path": [
                "$.type"
              ],
              "purpose": "Check if VC type is correct",
              "filter": {
                "type": "array",
                "contains": {
                  "type": "string",
                  "pattern": "^Email$"
                }
              }
            },
            {
              "path": [
                "$.credentialSubject.email"
              ],
              "purpose": "Check if VC contains email field",
              "filter": {
                "type": "string"
              }
            },
            {
              "path": [
                "$.issuer"
              ],
              "purpose": "Check if VC Issuer is Trusted",
              "filter": {
                "type": "string",
                "pattern": "^did:key:zQ3shtMGCU89kb2RMknNZcYGUcHW8P6Cq3CoQyvoDs7Qqh33N"
              }
            }
          ]
        }
      },
      {
        "id": "profile_vc",
        "name": "Country VC",
        "purpose": "Check if data contains necessary fields",
        "group": ["A"],
        "constraints": {
          "fields": [
            {
              "path": [
                "$.type"
              ],
              "purpose": "Check if VC type is correct",
              "filter": {
                "type": "array",
                "contains": {
                  "type": "string",
                  "pattern": "^HITCountry$"
                }
              }
            },
            {
              "path": ["$.credentialSubject.country"]
            }
          ]
        }
      }
    ]
},
"idTokenMapping": [
  {
    "sourceField": "$.type",
    "idTokenClaim": "$.custom[0].type",
    "inputDescriptorId": "email_vc"
  },
  {
    "sourceField": "$.credentialSubject.email",
    "idTokenClaim": "$.custom[1].email",
    "inputDescriptorId": "email_vc"
  },
  {
    "sourceField": "$.credentialSubject.country",
    "idTokenClaim": "$.custom[2].country",
    "inputDescriptorId": "profile_vc"
  }
]

ID Token Details

Below is the resulting ID Token for this example:

{
    "acr": "0",
    "at_hash": "R8YwamO1HFMT3Nf-hBMg-w",
    "aud": [
        "e7e54cff-1640-4f9b-878u-d8b294a2267c"
    ],
    "auth_time": 1698815469,
    "custom": [
        {
          "type": [
            "VerifiableCredential",
            "Email"
          ]
        },
        {
          "email": "email@email.com"
        },
        {
          "country": "Singapore"
        },
        {
          "did": "did:key..."
        }
    ],
    "exp": 1698816371,
    "iat": 1698815471,
    "iss": "https://<PROJECT_ID>.apse1.login.affinidi.io",
    "jti": "c77b819d-f4cf-4771-8977-c4c482287569",
    "rat": 1698815462,
    "sid": "2e86778d-83af-47d9-jai8-1f928e2743bc",
    "sub": "did:key..."
}

Updating Presentation Definition and ID Token Mapping

You can modify and extend the default Presentation Definition and ID Token Mapping defined in the Login Configuration by adding more fields or setting specific conditions to the data requested by your application to the user’s Affinidi Vault.

You can customise ID Token following the standards claims depending on the requirements of your application. Use this guide to learn more about defining top-level claims in the ID Token.

Using Affinidi Portal

Using  Affinidi Portal PEX Editor, developers has the option to modify the Presentation Definition when creating or after creating the Login Configuration. To do this, you have to expand the Additional Configuration section and open on the PEX editor:

PEX Editor Affinidi Portal

Additionally, developers have the option to reset to the default presentation in case there’s an issue while modifying the presentation.

You can read more about the Affinidi Portal PEX Editor in this guide.

Using Affinidi CLI

Using Affinidi CLI, developers can modify the default Presentation Definition and ID Token Mapping after creating the Login Configuration. To do this, follow the steps below:

First, retrieve the Login Configuration you wish to update using this command:

affinidi login get-config --id <LOGIN_CONFIG_ID>

Extract the presentationDefinition and idTokenMapping properties from the response and save them into a JSON file.

You may skip this if you already have the formatted JSON file with the modified Presentation Definition and ID Token Mapping.

Modify the JSON file based on your requirements and update the Login Configuration using this command:

affinidi login update-config --id <LOGIN_CONFIG_ID> --file <JSON_PAYLOAD_FILE>
Sample JSON Payload File

JSON payload file will look in the below format based on the sample modification we did previously:

"presentationDefinition": {
    "id": "vp_combined_email_user_profile_combined",
    "submission_requirements": [
      {
        "rule": "pick",
        "min": 1,
        "from": "A"
      }
    ],
    "input_descriptors": [
      {
        "id": "email_vc",
        "name": "Email VC",
        "purpose": "Check if data contains necessary fields",
        "group": ["A"],
        "constraints": {
          "fields": [
            {
              "path": [
                "$.type"
              ],
              "purpose": "Check if VC type is correct",
              "filter": {
                "type": "array",
                "contains": {
                  "type": "string",
                  "pattern": "^Email$"
                }
              }
            },
            {
              "path": [
                "$.credentialSubject.email"
              ],
              "purpose": "Check if VC contains email field",
              "filter": {
                "type": "string"
              }
            },
            {
              "path": [
                "$.issuer"
              ],
              "purpose": "Check if VC Issuer is Trusted",
              "filter": {
                "type": "string",
                "pattern": "^did:key:zQ3shtMGCU89kb2RMknNZcYGUcHW8P6Cq3CoQyvoDs7Qqh33N"
              }
            }
          ]
        }
      },
      {
        "id": "profile_vc",
        "name": "Country VC",
        "purpose": "Check if data contains necessary fields",
        "group": ["A"],
        "constraints": {
          "fields": [
            {
              "path": [
                "$.type"
              ],
              "purpose": "Check if VC type is correct",
              "filter": {
                "type": "array",
                "contains": {
                  "type": "string",
                  "pattern": "^HITCountry$"
                }
              }
            },
            {
              "path": ["$.credentialSubject.country"]
            }
          ]
        }
      }
    ]
},
"idTokenMapping": [
  {
    "sourceField": "$.type",
    "idTokenClaim": "$.custom[0].type",
    "inputDescriptorId": "email_vc"
  },
  {
    "sourceField": "$.credentialSubject.email",
    "idTokenClaim": "$.custom[1].email",
    "inputDescriptorId": "email_vc"
  },
  {
    "sourceField": "$.credentialSubject.country",
    "idTokenClaim": "$.custom[2].country",
    "inputDescriptorId": "profile_vc"
  }
]

After the successful update, the next time the user triggers the Affinidi Login on the website, it will use the updated Presentation Definition and ID Token Mapping to get consent to share data and verify the user.

Presentation Definition through PEX Query is a powerful tool that allows you to define your data requirements that must be satisfied by the user to be able to authenticate to your application. At the same time, ID Token Mapping allows you to extend the user claims without modifying the logic of your application.