Skip to main content

Become a relying party

Intermediate
Tutorial

Overview

A relying party is a service or app that requests a user's verifiable credentials from an issuer. Relying parties use an identity provider, such as Internet Identity, to communicate with the issuer. Identity providers must support the verifiable credentials specification. Communication with the identity provider uses the window.postMessage() communication channel.

The relying party must perform two main tasks:

  • Request the credentials from the issuer through an identity provider.
  • Verify the credentials received from an identity provider.

Requesting credentials

There are two ways to request credentials:

JavaScript SDK for verifiable credentials

You can request credentials to issuers through identity providers with the JS client for relying parties NPM package @dfinity/verifiable-credentials. Below is an example:

import { requestVerifiablePresentation } from "@dfinity/verifiable-credentials/request-verifiable-presentation";

requestVerifiablePresentation({
onSuccess: async (verifiablePresentation: VerifiablePresentationResponse) => {
// Called when the flow finishes successfully.
},
onError() {
// Called when there is a technical error.
},
issuerData: {
origin: "<url of the origin>",
canisterId: "<[optional] canister id>",
},
credentialData: {
credentialSpec: {
credentialType: '<credential type as expected by issuer>',
arguments: {
// Arguments to verify with the credential
},
},
credentialSubject: "<user's principal>",
},
identityProvider: "<[optional] url identity provider>",
derivationOrigin: "<[optional] origin for delegated identity>",
windowOpenerFeatures: "<[optional] window opener config string>",
});

Learn more about the JavaScript SDK for VCs.

This code executes the following steps:

  • Opens a new window or tab with the identity provider (Internet Identity (II)).

  • Waits for a window post message from II.

  • Sends a request to II through the window post message.

  • Waits for the response from II.

  • Calls an onSuccess callback if the process was successful, but that does not necessarily mean that a credential was received.

  • Calls an onError callback if the process has some technical error, if the user closes the II window, or if II times out.

Learn more about how this workflow works.

Manual integration

To implement a relying party service, your canister must perform three steps:

  1. Load an identity provider such as Internet Identity: Open a window to an identity provider that will act as the communication bridge between the relying party and the issuer.

  2. Request credentials: Send a JSON RPC request to the issuer API for a user's credentials.

  3. Receive a response: Receive either a success or error message from the identity provider.

Step 1: Load the identity provider in a new window

First, your canister must initiate and load an identity provider (in this example, Internet Identity) in a new window with the URL path /vc-flow. Opening this window will trigger the identity provider to load and send a notification to the window.postMessage() interface with the following JSON-RPC content:

{
"jsonrpc": "2.0",
"method": "vc-flow-ready"
}

The user will interact with this window to authenticate with their Internet Identity. If this window is closed by the user or times out, the request for credentials will be closed and an error will be returned to the relying party.

Step 2: Request a verifiable credential

Once the user has logged into Internet Identity and the service is ready, the relying party service can request a verifiable credential for the user by sending a JSON RPC request to the issuer API. The user must be logged into Internet Identity for the request to be successful. Below is an example of a JSON RPC request to request a credential:

{
"id": 1,
"jsonrpc": "2.0",
"method": "request_credential",
"params": {
"issuer": {
"origin": "https://employment-info.com",
"canisterId": "rwlgt-iiaaa-aaaaa-aaaaa-cai"
},
"credentialSpec": {
"credentialType": "VerifiedEmployee",
"arguments": {
"employerName": "XYZ Ltd."
}
},
"credentialSubject": "2mdal-aedsb-hlpnv-qu3zl-ae6on-72bt5-fwha5-xzs74-5dkaz-dfywi-aqe"
}
}

In this request, the following parameters must be set:

  • method: Pass the expected method "request_credential".

  • params: Configuration parameters to identify which credential is being requested.

    • issuer: The information to identify which issuer API instance the request is sent to.

      • origin: The origin URL of the issuer.

      • canisterId: The canister ID of the issuer API. This field is optional.

    • credentialSpec: The specification of the credential being requested.

      • credentialType: The type of the requested credential.

      • arguments: Key value pairs that are specific to the credential. This field is optional.

    • credentialSubject: The principal of the user requesting the credential.

    • derivationOrigin: An origin that should be used for the relying party's principal derivation as an alternative to the frontend hostname. This should match the derivationOrigin used when logging in the relying party.

Additional examples can be found in the verifiable credentials specification.

Step 3: Receive a response

If the JSON RPC request is successfully sent and received by the issuer API, Internet Identity will return the following JSON-RPC response to the relying party:

{
"id": 1,
"jsonrpc": "2.0",
"result": {
"verifiablePresentation": "eyJQ..."
}
}

In this response, verifiablePresentation is a JWT-based verifiable presentation (VP) containing two credentials:

  1. A verifiable credential issued by Internet Identity that relates the credentialSubject to the user principal.

  2. A verifiable credential issued by the issuer to the user principal.

The specification ensures unlinkability between the relying party and the issuer. That's why two credentials are needed. The first credential proves the link between the relying party's principal and id_alias created in the identity provider. The second credential is the actual credential to the id_alias.

Relying Party workflow

Note how the issuer's principal never appears in the credentials. Instead, the relying party and the issuer know about the id_alias. Therefore, this id_alias needs the credential to verify the link between the user's principal and the id_alias.

The order of the credentials in the VP should match the order above, such that the id_alias credential linking to the relying party's principal should come first.

View an example of a verifiable presentation for reference.

The relying party should not expect to receive an immediate response, as it should wait for one of the following events:

  • II sends a JSON-RPC response as described above.

  • II sends a JSON-RPC error response as described below.

  • The user closes the II window. This should be treated as an error.

The relying party may also close the II window after some timeout. The user would then be notified by the relying party that the request failed.

Error messages

If the workflow fails for any reason, Internet Identity will return an error. The error does not provide details on the cause of the failure to provide privacy protection.

An example error can be found below:

{
"id": 1,
"jsonrpc": "2.0",
"error": {
"version": "1",
"code": "UNKNOWN"
}
}

Verifying the credentials

To verify the credentials, the relying party needs to validate the JWT received from the identity provider.

Credentials format

The relying party receives the credential in a JWT format, containing long text split into common parts:

<header>.<payload>.

The ICP verifiable credentials feature is based on the w3c VC credentials data model.

There is no signature in this first presentation.

When this is decoded, the format is the following:

{
"iss": <issuer>,
"vp": {
"@context": "https://www.w3.org/2018/credentials/v1",
"type": "VerifiablePresentation",
"verifiableCredential": [
"<header>.<payload>.<signature>",
"<header>.<payload>.<signature>"
]
}

The field verifiableCredential within vp should contain two more verifiable credentials in the specified order:

  • An id_alias credential which links the effective subject of the credential to a temporary alias. This credential is signed by the identity provider.

  • The actual credential requested by the user. The subject of this credential is id_alias, and it should be signed by the issuer.

When the id_alias credential is decoded, the following info is returned:

{
"exp": <expiration>,
"iss": "https://identity.ic0.app/",
"nbf": <not-before>,
"jti": <jwt-id>,
"sub": <relying-party-user-principal>
"vc": {
"@context": "https://www.w3.org/2018/credentials/v1",
"type": [
"VerifiableCredential",
"InternetIdentityIdAlias"
],
"credentialSubject": {
"InternetIdentityIdAlias": {
"hasIdAlias": <id-alias>
}
}
}

When the actual credential decoded, the following info is returned:

{
"exp": <expiration>,
"iss": <isuer>,
"nbf": <not-before>,
"jti": <jwt-id>,
"sub": <did-id-alias>,
"vc": {
"@context": "https://www.w3.org/2018/credentials/v1",
"type": [
"VerifiableCredential",
"<credential-type>"
],
"credentialSubject": {
<credential-type>: {
<argument1-key>: <argument1-value>,
<argument2-key>: <argument2-value>,
...
}
}
}
}

Cryptographic verification

To verify the two JWT signatures, verify the signature of the id_alias signed by the identity provider, and the signature of the actual credential signed by the issuer. Both are found when you decode the received JWT.

These signatures are canister signatures and there is an example on how to verify those in the Internet Identity repository.

Semantic verification

The relying party should verify the following data:

  • From the received credential JWT:

    • The iss matches the identity provider canister ID.

    • The decoded initial credential contains two credentials within vp.verifiableCredential: first the id_alias credential, then the requested one.

  • From the id_alias JWT:

    • The iss is the expected identity provider.

    • The sub in id_alias contains the principal of the user in the relying party.

    • The credential type is InternetIdentityIdAlias.

  • From the requested credential JWT:

    • The credential type within vc.type matches the first field in vc.credentialSubject and both are the same that was requested.

    • The arguments within vc.credentialSubject.<credential-type> are the expected ones.

  • Cross credentials:

    • The sub from the requested credential contains the value in the id_alias credential from vc.credentialSubject.InternetIdentityIdAlias.

To learn more about verifying credentials, check out the demo relying party's code and the helper used from the Internet Identity repository.

Resources