Revocable Credentials
Revocable credentials are Verifiable Credentials issued to users that the issuer can revoke when needed. As an issuer, sometimes you may need to revoke a credential issued to a particular user for reasons like:
- Membership credential is when the entitled user already has an expired membership or wishes to discontinue the membership.
- Employee credentials are when the employee is no longer with the company.
- Provided incorrect details in the issued credentials.
The credential revocation flow provided by the Credential Issuance service implements the Revocation List 2020 standard. When the user shares data from their Affinidi Vault through the Affinidi Iota Framework, for example, and if the Credential Verification option is enabled, it validates whether the Verifiable Credential shared by the user is still valid by checking the revocation status.
To issue a revocable credential, you must set the statusListsDetails
with the purpose of REVOCABLE
and the standard to use for revocation, in this case, RevocationList2020
. This allows you, as the issuer, to revoke the credential later.
Issue a Revocable Credential
Create a Credential Issuance configuration to issue revocable credentials and integrate your application using the Affinidi TDK Credential Issuance client.
In this example, we will create a Credential Offer for a membership use case. Follow the sample code below to initiate a Credential Offer to the user.
Note on Claim Modes
If you choose to create the Credential Offer with FIXED_HOLDER
claim mode, the credential issuance requires the user’s DID value as the Holder DID (holderDid
). To get the Holder DID, your website must implement Affinidi Login to authenticate and extract the user DID value from the ID Token provided.
You can explore our Labs to learn how to integrate Affinidi Login into different programming languages and frameworks.
Otherwise, use TX_CODE
, the default claim mode where Holder DID (holderDid
) is not required.
- Install the required Credential Issuance libraries.
npm install -S @affinidi-tdk/auth-provider @affinidi-tdk/credential-issuance-client
pip install affinidi_tdk_auth_provider affinidi_tdk_credential_issuance_client
composer require affinidi-tdk/affinidi-tdk-php
- Import the libraries into the code. We are importing the Credential Issuance client (Credential Issuance Service) to create a Credential Offer and the Auth Provider to generate the Project Scoped Token for the Authorisation header.
import { IssuanceApi, Configuration, StartIssuanceInput } from '@affinidi-tdk/credential-issuance-client'
import { AuthProvider } from '@affinidi-tdk/auth-provider'
import affinidi_tdk_auth_provider
import affinidi_tdk_credential_issuance_client
require_once 'vendor/autoload.php';
use AffinidiTdk\AuthProvider\AuthProvider;
use AffinidiTdk\Clients\CredentialIssuanceClient;
- Generate an Authorisation token to call the client using the Personal Access Token for the specific project.
Use the Affinidi CLI Token command to generate the Personal Access Token (PAT) for the Auth Provider.
If you have created the Personal Access Token (PAT) with the
--key-id
flag in Affinidi CLI, you must also set thekeyId
with the supplied value in the AuthProvider class.
// NOTE: set your variables for PAT
const privateKey = "<PAT_PRIVATE_KEY_STRING>"
const passphrase = "<PAT_KEY_PAIR_PASSPHRASE>"
const tokenId = "<PAT_ID>"
const projectId = "<PROJECT_ID>"
const authProvider = new AuthProvider({
privateKey,
passphrase,
tokenId,
projectId
})
const authConfiguration = new Configuration({
apiKey: authProvider.fetchProjectScopedToken.bind(authProvider)
})
stats = {
"privateKey": "<PAT_PRIVATE_KEY_STRING>",
"passphrase": "<PAT_KEY_PAIR_PASSPHRASE>",
"tokenId": "<PAT_ID>",
"projectId": "<PROJECT_ID>"
}
authProvider = affinidi_tdk_auth_provider.AuthProvider(stats)
projectScopedToken = authProvider.fetch_project_scoped_token()
configuration = affinidi_tdk_credential_issuance_client.Configuration()
# Configure API key authorization: ProjectTokenAuth
configuration.api_key['ProjectTokenAuth'] = projectScopedToken
$params = [
'privateKey' => "<PAT_PRIVATE_KEY_STRING>",
'passphrase' => '<PAT_KEY_PAIR_PASSPHRASE>',
'tokenId' => '<PAT_ID>',
'projectId' => '<PROJECT_ID>'
];
$authProvider = new AuthProvider($params);
$tokenCallback = [$authProvider, 'fetchProjectScopedToken'];
- Initiate the Issuance module with the authorisation header and call the client method with the credential data. Additionally, we have set the the credential to be revocable by adding the
statusListDetails
property.
credentialTypeId
is the value configured on your Credential Issuance Configuration for the Supported Schemas.
async function issueCredential() {
const api = new IssuanceApi(authConfiguration)
const projectId = "<Project_ID>"
const request: StartIssuanceInput = {
"data": [{
"credentialTypeId": "UniversityDegree2024",
"credentialData": {
"first_name": "FirstName",
"last_name": "LastName",
"course": "Fundamentals of Decentralised Identity",
"completion_date": "2024-01-01"
},
"statusListDetails": [{
"purpose": "REVOCABLE",
"standard": "RevocationList2020"
}]
}],
"claimMode": "TX_CODE"
}
const { data } = await api.startIssuance(projectId, request)
return data
}
issueCredential()
.then((data) => console.log(data))
.catch((error) => console.log(error))
with affinidi_tdk_credential_issuance_client.ApiClient(configuration) as api_client:
api_instance = affinidi_tdk_credential_issuance_client.IssuanceApi(api_client)
projectId = "<Project_ID>"
request_json = {
"data": [{
"credentialTypeId": "UniversityDegree2024",
"credentialData": {
"first_name": "FirstName",
"last_name": "LastName",
"course": "Fundamentals of Decentralised Identity",
"completion_date": "2024-01-01"
},
"statusListDetails": [{
"purpose": "REVOCABLE",
"standard": "RevocationList2020"
}]
}],
"claimMode": "TX_CODE"
}
start_issuance_input = affinidi_tdk_credential_issuance_client.StartIssuanceInput.from_dict(request_json)
api_response = api_instance.start_issuance(projectId, start_issuance_input=start_issuance_input)
// Configure API key authorization: ProjectTokenAuth
$config = CredentialIssuanceClient\Configuration::getDefaultConfiguration()->setApiKey('authorization', '', $tokenCallback);
$apiInstance = new CredentialIssuanceClient\Api\IssuanceApi(
new GuzzleHttp\Client(),
$config
);
try {
$projectId = "<Project_ID>";
$request = array(
"data" => array(
array(
"credentialTypeId" => "UniversityDegree2024",
"credentialData" => array(
"Firstname" => "FirstName",
"Lastname" => "LastName",
"course" => "Fundamentals of Decentralised Identity",
"completion_date" => "2024-01-01"
),
"statusListDetails" => array(
array(
"purpose" => "REVOCABLE",
"standard" => "RevocationList2020"
)
)
)
),
"claimMode" => "TX_CODE"
);
$result = $apiInstance->startIssuance($projectId, $request);
return $result;
} catch (Exception $e) {
echo 'Exception when calling method: ', $e->getMessage(), PHP_EOL;
}
The Issuance Service will return the Credential Offer URI and the Transaction Code to obtain the credential details. Your application should send this information securely to the intended user to claim the Credential Offer within the configured duration in the Issuance Configuration.
{
"credentialOfferUri": "https://<PROJECT_ID>.apse1.issuance.affinidi.io/offer/<ISSUANCE_ID>",
"txCode": "123456",
"issuanceId": "<ISSUANCE_ID>",
"expiresIn": 3600
}
Revoke a Credential
To revoke a revocable credential issued by your project, use the associated Credential Issuance configuration to get the list of issued credentials. Only the revocable credentials can be revoked to invalidate the Verifiable Credentials when requested and verified by the requester.
To revoke a credential, in this case, the membership VC issued earlier, follow the steps below:
- Install the required Credential Issuance libraries.
npm install -S @affinidi-tdk/auth-provider @affinidi-tdk/credential-issuance-client
pip install affinidi_tdk_auth_provider affinidi_tdk_credential_issuance_client
composer require affinidi-tdk/affinidi-tdk-php
- Import the libraries into the code. We are importing the Credential Issuance client (Credential Issuance Service) to list and revoke the credential record and the Auth Provider to generate the Project Scoped Token for the Authorisation header.
import { IssuanceApi, Configuration, ChangeCredentialStatusInput } from '@affinidi-tdk/credential-issuance-client'
import { AuthProvider } from '@affinidi-tdk/auth-provider'
import affinidi_tdk_auth_provider
import affinidi_tdk_credential_issuance_client
require_once 'vendor/autoload.php';
use AffinidiTdk\AuthProvider\AuthProvider;
use AffinidiTdk\Clients\CredentialIssuanceClient;
- Generate an Authorisation token to call the client using the Personal Access Token for the specific project.
Use the Affinidi CLI Token command to generate the Personal Access Token (PAT) for the Auth Provider.
If you have created the Personal Access Token (PAT) with the
--key-id
flag in Affinidi CLI, you must also set thekeyId
with the supplied value in the AuthProvider class.
// NOTE: set your variables for PAT
const privateKey = "<PAT_PRIVATE_KEY_STRING>"
const passphrase = "<PAT_KEY_PAIR_PASSPHRASE>"
const tokenId = "<PAT_ID>"
const projectId = "<PROJECT_ID>"
const authProvider = new AuthProvider({
privateKey,
passphrase,
tokenId,
projectId
})
const authConfiguration = new Configuration({
apiKey: authProvider.fetchProjectScopedToken.bind(authProvider)
})
stats = {
"privateKey": "<PAT_PRIVATE_KEY_STRING>",
"passphrase": "<PAT_KEY_PAIR_PASSPHRASE>",
"tokenId": "<PAT_ID>",
"projectId": "<PROJECT_ID>"
}
authProvider = affinidi_tdk_auth_provider.AuthProvider(stats)
projectScopedToken = authProvider.fetch_project_scoped_token()
configuration = affinidi_tdk_credential_issuance_client.Configuration()
# Configure API key authorization: ProjectTokenAuth
configuration.api_key['ProjectTokenAuth'] = projectScopedToken
$params = [
'privateKey' => "<PAT_PRIVATE_KEY_STRING>",
'passphrase' => '<PAT_KEY_PAIR_PASSPHRASE>',
'tokenId' => '<PAT_ID>',
'projectId' => '<PROJECT_ID>'
];
$authProvider = new AuthProvider($params);
$tokenCallback = [$authProvider, 'fetchProjectScopedToken'];
- Get your project’s issued credential list using the
projectId
andconfigurationId
.
const api = new IssuanceApi(authConfiguration)
const projectId = "<Project_ID>"
const configId = "<Config_ID>"
const { data } = await api.listIssuanceDataRecords(projectId, configId)
with affinidi_tdk_credential_issuance_client.ApiClient(configuration) as api_client:
api_instance = affinidi_tdk_credential_issuance_client.IssuanceApi(api_client)
projectId = "<Project_ID>"
configId = "<Config_ID>"
api_response = api_instance.list_issuance_data_records(projectId, configId)
// Configure API key authorization: ProjectTokenAuth
$config = CredentialIssuanceClient\Configuration::getDefaultConfiguration()->setApiKey('authorization', '', $tokenCallback);
$apiInstance = new CredentialIssuanceClient\Api\DefaultApi(
new GuzzleHttp\Client(),
$config
);
try {
$projectId = "<Project_ID>";
$configId = "<Config_ID>";
$result = $apiInstance->listIssuanceDataRecords($projectId, $configId);
} catch (Exception $e) {
echo 'Exception when calling method: ', $e->getMessage(), PHP_EOL;
}
This step will return the list of credential records issued from your project. See the sample response below:
{
"createdAt": "2024-12-20T06:58:25.204Z",
"modifiedAt": "2024-12-20T06:58:25.204Z",
"id": "c5bc063d-0ba0-4a9f-8054-09aa46f7b8b9", // Record ID
"projectId": "cba41b90-e3bb-4687-a2cf-800baa8765f6",
"flowId": "a8c71f56-e68c-41bf-9fb5-309140209876", // Match with Issuance ID
"credentialTypeId": "Membership",
"jsonLdContextUrl": "https://schema.affinidi.io/TMembershipV1R0.jsonld",
"jsonSchemaUrl": "https://schema.affinidi.io/TMembershipV1R0.json",
"configurationId": "3533a6e9-320f-4c90-881d-99a07210f8a1",
"issuedAt": "",
"walletId": "86d87d4cfba78133864749518chy7gt6",
"projectIdConfigurationId": "cba41b90-e3bb-4687-a2cf-800baa8765f6#3533a6e9-320f-4c90-881d-99a07210f8a1",
"projectIdConfigurationIdWalletId": "cba41b90-e3bb-4687-a2cf-800baa8765f6#3533a6e9-320f-4c90-881d-99a07210f8a1#86d87d4cfba78133864749518chy7gt6",
"projectIdConfigurationIdCredentialType": "cba41b90-e3bb-4687-a2cf-800baa8765f6#3533a6e9-320f-4c90-881d-99a07210f8a1#Membership",
"statusListsDetails": [
{
"statusListPurpose": "REVOKED",
"statusListId": "",
"statusListIndex": "",
"standard": "RevocationList2020",
"isActive": true,
"statusActivationReason": "",
"statusActivatedAt": ""
}
]
}
After retrieving the records of the credentials issued from your project, loop through the list and find the record that matches the
flowId
with theissuanceId
returned when you created the credential offer.Once you match the
flowId
with theissuanceId
, extract the ID value from the list and use it as a parameter to request to revoke the credential.
Note
ThechangeReason
parameter only accepts either INVALID_CREDENTIAL
or COMPROMISED_ISSUER
as the value. const api = new DefaultApi(authConfiguration)
const projectId = "<Project_ID>"
const configId = "<Config_ID>"
const request: ChangeCredentialStatusInput = {
"changeReason": "INVALID_CREDENTIAL",
"issuanceRecordId": "<Record_ID>"
}
const { data } = await api.changeCredentialStatus(projectId, configId, request)
with affinidi_tdk_credential_issuance_client.ApiClient(configuration) as api_client:
api_instance = affinidi_tdk_credential_issuance_client.DefaultApi(api_client)
projectId = "<Project_ID>"
configId = "<Config_ID>"
request_json = {
"changeReason": "INVALID_CREDENTIAL",
"issuanceRecordId": "<Record_ID>"
}
change_credential_status_input = affinidi_tdk_credential_issuance_client.ChangeCredentialStatusInput.from_dict(request_json)
api_response = api_instance.change_credential_status(projectId, configId, change_credential_status_input=change_credential_status_input)
// Configure API key authorization: ProjectTokenAuth
$config = CredentialIssuanceClient\Configuration::getDefaultConfiguration()->setApiKey('authorization', '', $tokenCallback);
$apiInstance = new CredentialIssuanceClient\Api\DefaultApi(
new GuzzleHttp\Client(),
$config
);
try {
$projectId = "<Project_ID>";
$configId = "<Config_ID>";
$request = array(
"changeReason" => "INVALID_CREDENTIAL",
"issuanceRecordId" => "<Record_ID>"
);
$result = $apiInstance->changeCredentialStatus($projectId, $configId, $request);
return $result;
} catch (Exception $e) {
echo 'Exception when calling method: ', $e->getMessage(), PHP_EOL;
}
After revoking the issued credential, if the credential is requested, it will return invalid, and the data sharing will fail.
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.