Decide ID provides proof of personhood verification for applications built on the Internet Computer Protocol (ICP). By leveraging ICP's Internet Identity verifiable credentials system, applications can verify that their users are unique individuals through Decide ID's verification service.
Prerequisites
Your application must be built on the Internet Computer Protocol
Install the DFINITY Verifiable Credentials SDK:
npm install @dfinity/verifiable-credentials
Integration Steps
1. Import Required Dependencies
import { requestVerifiablePresentation, VerifiablePresentationResponse } from "@dfinity/verifiable-credentials/request-verifiable-presentation";
import { Principal } from "@dfinity/principal";
2. Request Verification
To initiate the verification process, call requestVerifiablePresentation with the following configuration:
const requestVerification = async (verifyPrincipal: Principal): Promise<void> => {
try {
const jwt: string = await new Promise((resolve, reject) => {
requestVerifiablePresentation({
onSuccess: async (verifiablePresentation: VerifiablePresentationResponse) => {
if ('Ok' in verifiablePresentation) {
resolve(verifiablePresentation.Ok);
} else {
reject(new Error(verifiablePresentation.Err));
}
},
onError(err) {
reject(new Error(err));
},
issuerData: {
origin: 'https://id.decideai.xyz',
canisterId: Principal.fromText('qgxyr-pyaaa-aaaah-qdcwq-cai'),
},
credentialData: {
credentialSpec: {
credentialType: 'ProofOfUniqueness',
arguments: {
// Specify the minimum date when the user's Decide ID verification must have occurred
// Format: ISO 8601 timestamp
minimumVerificationDate: "2024-12-01T00:00:00Z",
},
},
credentialSubject: verifyPrincipal,
},
identityProvider: new URL('https://identity.ic0.app/'),
derivationOrigin: window.location.origin,
});
});
// Verify the JWT credentials received
await verifyCredentials(jwt);
} catch (error) {
console.error('Verification failed:', error);
throw error;
}
};
2. Frontend: Request Verification
The frontend code initiates the verification process and sends the received JWT to your backend canister:
Verification should be performed in your backend canister. Here are examples in both Rust and Motoko:
Rust Canister Example
#[update]
async fn verify_credential(jwt: String) -> Result<(), String> {
// Decode and verify JWT signature using IC crypto primitives
let decoded = decode_and_verify_jwt(&jwt)?;
// Extract and verify the credentials
let credentials = decoded.get_verifiable_credentials()?;
// Verify we have exactly two credentials (ID alias and PoH)
if credentials.len() != 2 {
return Err("Invalid credential structure".to_string());
}
// Verify the proof of uniqueness credential
let poh_credential = &credentials[1];
// Verify credential type
if !poh_credential.credential_type.contains("ProofOfUniqueness") {
return Err("Invalid credential type".to_string());
}
// Verify issuer is Decide ID canister
if poh_credential.issuer != Principal::from_text("qgxyr-pyaaa-aaaah-qdcwq-cai")? {
return Err("Invalid issuer".to_string());
}
// Verify the minimum verification date matches exactly what we requested
let received_date = poh_credential.get_minimum_verification_date()?;
let requested_date = "2024-12-10T00:00:00Z";
if received_date != requested_date {
return Err("Minimum verification date does not match requested date".to_string());
}
// Store the verified state
self.verified_users.insert(caller(), true);
Ok(())
}
Motoko Canister Example
public shared(msg) func verifyCredential(jwt : Text) : async Result<(), Text> {
// Decode and verify JWT signature using IC crypto primitives
let decoded = try {
DecodeJWT.decode(jwt);
} catch (e) {
return #err("Invalid JWT");
};
// Extract and verify the credentials
let credentials = decoded.verifiableCredentials;
// Verify we have exactly two credentials
if (credentials.size() != 2) {
return #err("Invalid credential structure");
};
// Verify the proof of uniqueness credential
let pohCredential = credentials[1];
// Verify credential type
if (not Array.find(pohCredential.types, func(t) { t == "ProofOfUniqueness" })) {
return #err("Invalid credential type");
};
// Verify issuer is Decide ID canister
if (pohCredential.issuer != Principal.fromText("qgxyr-pyaaa-aaaah-qdcwq-cai")) {
return #err("Invalid issuer");
};
// Verify the minimum verification date matches exactly what we requested
let receivedDate = pohCredential.getMinimumVerificationDate();
let requestedDate = "2024-12-10T00:00:00Z";
if (receivedDate != requestedDate) {
return #err("Minimum verification date does not match requested date");
};
// Store the verified state
verifiedUsers.put(msg.caller, true);
#ok()
};
These backend verification steps are crucial for security as they:
Cannot be tampered with by end users
Use secure IC cryptographic primitives
Can maintain authoritative state about verified users
Can integrate with your canister's other security measures
⚠️ Important: Never rely on client-side verification alone. Always verify credentials in your backend canister.
## Error Handling
The verification process can return several types of errors:
```typescript
type VerificationError = {
version: string;
code: 'UNKNOWN' | 'TIMEOUT' | 'USER_ABORT' | 'INVALID_CREDENTIALS';
};
// Handle errors appropriately in your application
const handleVerificationError = (error: VerificationError) => {
switch (error.code) {
case 'TIMEOUT':
// Handle timeout
break;
case 'USER_ABORT':
// Handle user cancellation
break;
case 'INVALID_CREDENTIALS':
// Handle invalid credentials
break;
default:
// Handle unknown errors
break;
}
};
Verification Date Requirements
The minimumVerificationDate parameter is a critical security feature that helps ensure the authenticity of user verifications. When specified, Decide ID will only accept verifications that were performed on or after the given date. This parameter is a measure that can be used to deter farmed accounts from accessing your platform as they will continuously need to ensure their accounts are reverified to comply with the minimum verification date.
Usage
minimumVerificationDate: "2024-01-01T00:00:00Z" // ISO 8601 format
Security Note
The minimumVerificationDate parameter in the credential request must be verified exactly in your backend canister. This ensures that:
The date hasn't been modified from what your application requested
Prevents tampering with the verification requirements
Maintains consistent security standards across your application
When verifying, don't just check if the date is recent enough - verify it matches exactly what you requested.
Configuration Tips
Set the date based on your application's security requirements
Consider updating the minimum date periodically
Use recent dates for higher-security applications
Balance security needs with user convenience
Best Practices
Error Handling: Implement comprehensive error handling for various failure scenarios.
User Experience: Provide clear feedback to users during the verification process.
Security: Always verify both the cryptographic signatures and semantic content of received credentials.
Caching: Consider caching verification results (with appropriate expiration) to improve user experience.
Security Considerations
Always verify the issuer's canister ID matches Decide ID's official canister
Implement proper session management for verified users