This blog post uses storytelling to introduce beginners to Verifiable Credentials, followed by a tutorial on creating them in JavaScript and Kotlin.
- In Our Last Issue...
- Meet Dawson's Boyfriend
- A New Way to Travel
- The Growing Popularity of Verifiable Credentials
- The Experiment
- What are Verifiable Credentials?
- Tutorial: Create a VC in JavaScript
- Tutorial: Create a VC in Kotlin
- Build Your Own VCs and Follow TBD's Progress
- Artist Credit
In Our Last Issue...
In our previous issue, we met Dawson Webhart, a junior frontend software engineer at a fashion tech company. We learned that Dawson's company wants to adopt Web5, a new technology that ensures users can control their privacy and data portability. Nadia, Dawson's bestie at work and in real life, hopes they lead the company's shift to Web5. Dawson also faced a personal setback when her favorite social media app took Dawson's username without her consent. These challenges piqued Dawson's interest in Web5, driving her to seek control over her digital presence.
Meet Dawson's boyfriend
When she gets home from work, Dawson pops a bag of popcorn and sits by the kitchen island, recapping her day with her boyfriend, Justin. As opposed to Dawson’s carefree and forgetful nature, Justin is prepared and meticulous, traits instilled by his immigrant parents. A physical trainer by profession, Justin's interest in the latest technological trends makes him the ideal sounding board for Dawson’s adventures.
“Dude, I can’t believe Francis Francino stole my handle. I’m never using that app again,” Dawson spills about her day, disbelief and frustration coloring her tone.
Justin nods empathetically, "It’s crazy how much of our data we don’t actually own on the internet, like your username situation. JungleGym games, the video game company, announced they're removing our access to certain games that no longer have tech support. It's ridiculous. If I had a physical copy, they couldn't just take it back. Makes me wish we could carry our digital purchases across different platforms."
Dawson lights up, "That's exactly what Nadia was saying about Web5—taking your data wherever you go. That’s why I’m thinking of really diving into it."
Justin shifts gears with a playful nudge, "Word, I support it. I also support you packing your bags for our weekend friend trip to Orlando. Our flight is tonight. Don't forget."
Caught off guard and trying to hide her forgetfulness, Dawson rushes upstairs to pack. “Psh, I did not forget! I just wanted to tell you about my day.”
She hastily packs, muttering under her breath about the number of outfits and shoes needed for a 2-day trip. And of course, she can’t forget her laptop.
Artist credit: scribblndibbl
A New Way to Travel
As they drive to the airport, Dawson runs through a checklist with Justin to ensure they haven't forgotten anything.
Artist credit: scribblndibbl
"Suitcase, carry-on, keys, debit card, and ID?" she inquires, ticking off each item.
"I left my ID at home. It's all on my phone now, remember? Nadia filled us in on Digital Travel Credentials. They are the next big thing in identity verification," Justin explains.
When they stop at a red light, Justin briefly pulls out his phone to show Dawson his Digital Travel Credentials.
Artist credit: scribblndibbl
“All I have to do is show this to the TSA and they will let me through,” he explains. Dawson shrugs. She was unimpressed and preferred the traditional method – her physical passport.
Artist credit: scribblndibbl
Dawson and Justin meet up with their friends, including Nadia and Nadia’s partner, at the airport. Everyone but Dawson swiftly passes through the Digital Travel Credentials (DTC) lane by showing their Verifiable Credential and tapping their phones on a card reader.
Artist credit: scribblndibbl
“Sorry, girl. You should’ve gotten your digital credentials like I mentioned in the group chat,” Nadia remarks as Dawson heads to the standard line.
Dawson sighs. The standard line is SO long, but then she remembers her CLEAR subscription, a speedier alternative. Her friends aren't the only smart ones, she chuckles to herself.
In the CLEAR queue, Dawson's turn comes quickly. She scans her eyes and smirks, appreciating the smooth process—no extra downloads or purchases necessary, contrary to her friends' approach. Then, a CLEAR employee halts her progress.
“Ma’am, you've been selected for random ID verification.”
Dawson rummages through her bag for her passport. Handing it over, the employee casually notes, “Oh, we share the same birthday! And you’re from the beautiful island of—”
“Yeah..can we just —,“ Dawson strains a smile, but she was cringing on the inside. She wanted to keep up with her friends, and she was disturbed by the unnecessary sharing of her personal details.
The Growing Popularity of Verifiable Credentials
Dawson reunites with her friends at the gate, venting about the invasive CLEAR employee. "You should report that," Nadia advises. "Next trip, get a Digital Travel Credential. The TSA didn't need to see my birth date or anything."
Nadia's enthusiasm grows as she elaborates on her motivation. "I pushed for everyone to try Digital Travel Credentials because I wanted to test them myself. They're a type of Verifiable Credential. Web5, the technology we're adopting at work, has a Software Development Kit to create credentials. Also, Verifiable Credentials are increasing in popularity. Companies like Microsoft, Auth0 by Okta, and Workday are all experimenting with Verifiable Credentials."
The Experiment
On the plane, Dawson immediately opens her laptop and connects to the in-flight wifi to learn about Verifiable Credentials.
Artist credit: scribblndibbl
What are Verifiable Credentials?
Verifiable Credentials (VCs) are digital proofs that confirm certain facts about individuals, organizations, or entities. They can look like digital badges. VCs are stored securely in your smartphone's wallet app—similar to a debit card in Apple Wallet or a gym membership in Google Wallet. At their core, VCs are protected by cryptography, specifically through a JSON Web Token (JWT), ensuring their authenticity and integrity.
Image credit: Auth0 Lab's Verifiable Credential Proof of Concept Designs
Flexible Use Cases of Verifiable Credentials
Let's say you need to prove you're of legal drinking age without revealing excess personal data. Instead of showing your full ID, you could present a VC to a vendor. The vendor recognizes the credential as proof of age and provides you with alcohol in exchange.
Similarly, Dawson's friends used Digital Travel Credentials to prove they were eligible to board their flight.
VCs can represent virtually any claim or certification, including but not limited to:
- Educational achievements
- Employment history
- Membership in clubs or organizations
- Authenticity and accuracy of content
- Online identity verification
- Trustworthiness as a seller or business partner
- Financial standing, including creditworthiness or proof of income, useful for loan applications. See: FormFree, a company using Web5's Verifiable Credentials to empower borrower's during the loan process.
Key Roles in the Verifiable Credential Process
There are typically three to four key players through the Verifiable Credential exchange process:
- Issuer: is a trusted organization, entity, or individual who created the verifiable credential and signed the credential stating the information is true.
- Subject: This is the organization, entity, or individual who the Verifiable Credential is about. They will store the verifiable credential in their phone's wallet and present it to the verifier.
- Verifier: This is the entity requesting proof. This entity evaluates the VC's validity and the issuer's credibility before granting access or services.
- Holder: Sometimes, a designated entity holds and presents the VC on the subject's behalf. Although, many times, the subject and the holder are the same.
The Flow
- The issuer creates a VC with claims about the subject.
- The issuer cryptographically signs the credential stating that the claims about the subject are true.
- The subject stores the VC in their phone’s wallet.
- The subject (or holder) presents their Verifiable Credential to a verifier.
- The verifier runs a series of checks to determine if the VC is real and valid. They also determine if the issuer is real and trustworthy.
What Does 'Cryptographically Signed' Mean?
I think of cryptography as using code to create a digital lock that protects information. When we say something is "cryptographically signed," it indicates that an item has been marked with a unique digital fingerprint. This is similar to back in the day when a king would seal a decree with a ring.
An algorithm generates the signature, ensuring it's unique to the document and the person who signed it. The digital fingerprint is composed of a private key and a public key. While your private key is kept a secret, your public key is shared with others to verify that the signature was made with your private key and the document hasn't been tampered with.
Together, these keys perform a dual function: they confirm the authenticity of the document and its signer AND maintain the integrity of the document by detecting any alterations post-signature.
Dawson wants to try creating a Legal Drinking Age Verifiable credential to help her apply the concepts she learned. Dawson starts by installing and importing the packages needed to create a Verifiable Credential with Web5. She runs the following command: She creates a file called Each participant in the Verifiable Credential process is linked to a Decentralized Identifier (DID), a W3C-standard alphanumeric string that serves as a unique identifier for individuals, organizations, or entities. Dawson thinks the Department of Motor Vehicles or the Passport Office could be potential Legal Drinking Age Credential issuers. For learning purposes, she creates a DID to simulate an issuer similar to the Department of Motor Vehicles, naming it "Fake Department of Motor Vehicles" or "FakeDMV." Subsequently, Dawson generates her own DID to act as the subject of the credential. Dawson examines her DID's details: She observes the DID document, particularly the DID string and public key: The "Fake DMV" signs the credential with their DID: Dawson prints out the signed verifiable credential, and she finds that the verifiable credential is now an odd alphanumeric string. She learns that signing her Verifiable Credential encoded it as a JSON Web Token (JWT), so that no one can modify the Verifiable Credential. When she pastes the value of the JWT in a tool like Auth0’s jwt.io, she can see the decoded value of the Verifiable Credential, including claims and decentralized identifiers. When you go to a verifier, you will share your Verifiable Credential with them. This is often done in a computerized way where maybe you scan a QR code or click a link. The verifier can check to verify that the Verifiable Credential is real by running the code below: Now, you can start the Presentation Exchange process. This is where your phone’s wallet and the Verifier’s application communicate. The verifier has a Presentation Definition. The Presentation Definition sets the specific criteria that the verifier is looking for through the Verifiable Credential. Dawson defines the criteria the verifier is looking for in the Presentation Definition: This setup allows for a verification process that checks the credential against the verifier's requirements.Tutorial: Create a VC in JavaScript
Package dependencies
npm install @web5/credentials @web5/dids
verifiable-creds.js
and imports the packages in that file:
import { DidDht } from '@web5/dids';
import { VerifiableCredential, PresentationExchange } from '@web5/credentials';
Creating the issuer and the subject
const fakeDmvIssuerDid = await DidDht.create();
const dawsonDid = await DidDht.create();
Inspect the DID
console.log(dawsonDid)
console.log(`this is the DID string ${dawsonDid.uri}`)
console.log(`this is the DID’s public key information ${dawsonDid.document.verificationMethod}`)
Creating the credential
class LegalDrinkingAgeCredential {
constructor(name, dateOfBirth, isOfLegalDrinkingAge, city, country) {
this.name = name;
this.dateOfBirth = dateOfBirth;
this.isOfLegalDrinkingAge = isOfLegalDrinkingAge;
this.city = city;
this.country = country;
}
}
const vc = await VerifiableCredential.create({
type: 'LegalDrinkingAgeCredential',
issuer: fakeDmvIssuerDid.uri,
subject: fakeDmvIssuerDid.uri,
data: new LegalDrinkingAgeCredential('Your Name', 'Your Date of Birth', true, 'Your City', 'Your Country')
});
Signing the credential
const signedVC = await vc.sign({ did: fakeDmvIssuerDid });
Inspect the Signed VC
eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSIsImtpZCI6ImRpZDpkaHQ6MWZmd25mNHduZW94cng5M2ZzYTF4NHA3eDg3Y210ZHJicWtkMXcxeTF4bmNtN2FrcWRleSMwIn0.eyJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSJdLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIiwiTGVnYWxEcmlua2luZ0FnZUNyZWRlbnRpYWwiXSwiaWQiOiJ1cm46dXVpZDo5ZGZhYjcyMS0xY2IwLTRmNDYtOTgxZi05YjhjZDZkY2E1NzUiLCJpc3N1ZXIiOiJkaWQ6ZGh0OjFmZnduZjR3bmVveHJ4OTNmc2ExeDRwN3g4N2NtdGRyYnFrZDF3MXkxeG5jbTdha3FkZXkiLCJpc3N1YW5jZURhdGUiOiIyMDI0LTAzLTA2VDA0OjExOjA2WiIsImNyZWRlbnRpYWxTdWJqZWN0Ijp7ImlkIjoiZGlkOmRodDoxZmZ3bmY0d25lb3hyeDkzZnNhMXg0cDd4ODdjbXRkcmJxa2QxdzF5MXhuY203YWtxZGV5IiwibmFtZSI6IllvdXIgTmFtZSIsImRhdGVPZkJpcnRoIjoiWW91ciBEYXRlIG9mIEJpcnRoIiwiaXNPZkxlZ2FsRHJpbmtpbmdBZ2UiOnRydWUsImNpdHkiOiJZb3VyIENpdHkiLCJjb3VudHJ5IjoiWW91ciBDb3VudHJ5In19LCJpc3MiOiJkaWQ6ZGh0OjFmZnduZjR3bmVveHJ4OTNmc2ExeDRwN3g4N2NtdGRyYnFrZDF3MXkxeG5jbTdha3FkZXkiLCJzdWIiOiJkaWQ6ZGh0OjFmZnduZjR3bmVveHJ4OTNmc2ExeDRwN3g4N2NtdGRyYnFrZDF3MXkxeG5jbTdha3FkZXkifQ.4_wXY3bWZvnvxqUIVsYkqHvrXQmmyYG9FTV7Zydq893yArGaZYCjia81KGFk4EQ241uaKP9i9dwaoeI_fcpJCg
Showing your credentials to a verifier
try {
await VerifiableCredential.verify({ vcJwt: signedVC });
console.log('\nVC Verification successful!\n');
} catch (err) {
console.log('\nVC Verification failed: ' + err.message + '\n');
}
const presentationDefinition = {
id: "presDefId123",
name: "Legal Drinking Age Presentation Definition",
purpose: "for verifying legal drinking age",
input_descriptors: [
{
id: "legalDrinkingAge",
purpose: "Are you of legal drinking age??",
constraints: {
fields: [
{
path: ["$.credentialSubject.isOfLegalDrinkingAge"],
},
],
},
},
],
};
try {
PresentationExchange.satisfiesPresentationDefinition({
vcJwts: signedVC,
presentationDefinition: presentationDefinition
});
} catch (err) {
console.log('Presentation Definition not satisfied: ' + err.message + '\n');
}
Dawson wants to try creating a Legal Drinking Age Verifiable credential to help her apply the concepts she learned. Dawson starts by installing and importing the packages needed to create a Verifiable Credential with Web5. She adds the following code to her pom.xml file: She creates a file called Each participant in the Verifiable Credential process is linked to a Decentralized Identifier (DID), a W3C-standard alphanumeric string that serves as a unique identifier for individuals, organizations, or entities. For the legal drinking age credential, Dawson thinks the following organizations could be potential issuers such as the Department of Motor Vehicles or the Passport Office. For learning purposes, she creates a DID to simulate an issuer similar to the Department of Motor Vehicles, naming it "Fake Department of Motor Vehicles" or "FakeDMV". Subsequently, Dawson generates her own DID to act as the subject of the credential. Dawson examines her DID's details: She observes the DID document, particularly the DID string and public key: The "Fake DMV" signs the credential with their DID: Dawson prints out the signed verifiable credential, and she finds that the verifiable credential is now an odd alphanumeric string. She learns that signing her Verifiable Credential encoded it as a JSON Web Token (JWT), so that no one can modify the Verifiable Credential. When she pastes the value of the JWT in a tool like Auth0’s jwt.io, she can see the decoded value of the Verifiable Credential including claims and decentralized identifiers. When you go to a verifier, you will share your Verifiable Credential with them. This is often done in a computerized way where maybe you scan a QR code or click a link. The verifier can check to verify that the Verifiable Credential is real by running the code below: Now, you can start the Presentation Exchange process. This is where your phone’s wallet and the Verifier’s application communicate. The verifier has a Presentation Definition. The Presentation Definition sets the specific criteria that the verifier is looking for through the Verifiable Credential. Dawson defines the criteria the verifier is looking for in the Presentation Definition: This setup allows for a verification process that checks the credential against the verifier's requirements.Tutorial: Create a VC in Kotlin
Package dependencies
<repositories>
<repository>
<id>mavenCentral</id>
<url>https://repo1.maven.org/maven2/</url>
</repository>
<repository>
<id>danubetech-maven-public</id>
<url>https://repo.danubetech.com/repository/maven-public/</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>xyz.block</groupId>
<artifactId>web5-credentials</artifactId>
<version>0.0.11</version>
</dependency>
<dependency>
<groupId>xyz.block</groupId>
<artifactId>web5-dids</artifactId>
<version>0.0.11</version>
</dependency>
</dependencies>
VerifiableCreds.kt
and imports the packages in that file:
import web5.sdk.crypto.InMemoryKeyManager
import web5.sdk.dids.methods.key.DidDht
import web5.sdk.dids.DidResolvers
import web5.sdk.credentials.VerifiableCredential
import web5.sdk.credentials.PresentationExchange
import web5.sdk.credentials.model.*
Creating the issuer and the subject
val fakeDmvIssuerDid = DidDht.create(InMemoryKeyManager())
val dawsonDid = DidDht.create(InMemoryKeyManager())
Inspect the DID
val resolvedDid = DidResolvers.resolve(dawsonDid.uri)
println(resolvedDid)
println("this is the DID string ${dawsonDid.uri}")
println("this is the DID’s public key information ${dawsonDid.didDocument?.verificationMethods}")
Creating the credential
data class LegalDrinkingCredential(
val name: String,
val dateOfBirth: String,
val isOfLegalDrinkingAge: Boolean,
val city: String,
val country: String
)
val vc = VerifiableCredential.create(
type = "LegalDrinkingCredential",
issuer = fakeDmvIssuerDid.uri,
subject = dawsonDid.uri,
data = LegalDrinkingCredential(
name = "Dawson Webhart",
dateOfBirth = "Your Date of Birth",
isOfLegalDrinkingAge = true
city = "Your City",
country = "Your Country"
)
)
Signing the credential
val signedVC = vc.sign(fakeDmvIssuerDid)
Inspect the Signed VC
eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSIsImtpZCI6ImRpZDpkaHQ6MWZmd25mNHduZW94cng5M2ZzYTF4NHA3eDg3Y210ZHJicWtkMXcxeTF4bmNtN2FrcWRleSMwIn0.eyJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSJdLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIiwiTGVnYWxEcmlua2luZ0FnZUNyZWRlbnRpYWwiXSwiaWQiOiJ1cm46dXVpZDo5ZGZhYjcyMS0xY2IwLTRmNDYtOTgxZi05YjhjZDZkY2E1NzUiLCJpc3N1ZXIiOiJkaWQ6ZGh0OjFmZnduZjR3bmVveHJ4OTNmc2ExeDRwN3g4N2NtdGRyYnFrZDF3MXkxeG5jbTdha3FkZXkiLCJpc3N1YW5jZURhdGUiOiIyMDI0LTAzLTA2VDA0OjExOjA2WiIsImNyZWRlbnRpYWxTdWJqZWN0Ijp7ImlkIjoiZGlkOmRodDoxZmZ3bmY0d25lb3hyeDkzZnNhMXg0cDd4ODdjbXRkcmJxa2QxdzF5MXhuY203YWtxZGV5IiwibmFtZSI6IllvdXIgTmFtZSIsImRhdGVPZkJpcnRoIjoiWW91ciBEYXRlIG9mIEJpcnRoIiwiaXNPZkxlZ2FsRHJpbmtpbmdBZ2UiOnRydWUsImNpdHkiOiJZb3VyIENpdHkiLCJjb3VudHJ5IjoiWW91ciBDb3VudHJ5In19LCJpc3MiOiJkaWQ6ZGh0OjFmZnduZjR3bmVveHJ4OTNmc2ExeDRwN3g4N2NtdGRyYnFrZDF3MXkxeG5jbTdha3FkZXkiLCJzdWIiOiJkaWQ6ZGh0OjFmZnduZjR3bmVveHJ4OTNmc2ExeDRwN3g4N2NtdGRyYnFrZDF3MXkxeG5jbTdha3FkZXkifQ.4_wXY3bWZvnvxqUIVsYkqHvrXQmmyYG9FTV7Zydq893yArGaZYCjia81KGFk4EQ241uaKP9i9dwaoeI_fcpJCg
Showing your credentials to a verifier
try {
VerifiableCredential.verify(signedVC)
} catch (err: Exception) {
println("VC Verification failed:" + err.message)
}
val presentationDefinition = PresentationDefinitionV2(
id = "presDefId123",
name = "Legal Drinking Age Presentation Definition",
purpose = "for verifying legal drinking age",
inputDescriptors = listOf(
InputDescriptorV2(
id = "legalDrinkingAge",
purpose = "Are you of legal drinking age?",
constraints = ConstraintsV2(
fields = listOf(
FieldV2(
path = listOf("$.vc.credentialSubject.isOfLegalDrinkingAge")
)
)
)
)
)
)
try {
PresentationExchange.satisfiesPresentationDefinition(
vcJwts = signedVC,
presentationDefinition = presentationDefinition
)
} catch (e: Exception) {
println("VC does not satisfy Presentation Definition: " + err.message)
}
Dawson taps a half-asleep Justin as they land in Orlando, "The planes landing, and I just made my first Verifiable Credentials in JavaScript and Kotlin," she says with a grin.
Please note: The information in this tutorial may become outdated as we update our SDKs towards the 1.0 release.
Build your own VCs and follow TBD's progress as we build Web5
Artist Credit
I did not do any of the art. My talented little sister drew all the art. Check out her work at: https://linktr.ee/scribblndibbl