Free 30-min Web3 Consultation
Book Consultation
Smart Contract Security Audits
View Audit Services
Custom DeFi Protocol Development
Explore DeFi
Full-Stack Web3 dApp Development
View App Services
Free 30-min Web3 Consultation
Book Consultation
Smart Contract Security Audits
View Audit Services
Custom DeFi Protocol Development
Explore DeFi
Full-Stack Web3 dApp Development
View App Services
Free 30-min Web3 Consultation
Book Consultation
Smart Contract Security Audits
View Audit Services
Custom DeFi Protocol Development
Explore DeFi
Full-Stack Web3 dApp Development
View App Services
Free 30-min Web3 Consultation
Book Consultation
Smart Contract Security Audits
View Audit Services
Custom DeFi Protocol Development
Explore DeFi
Full-Stack Web3 dApp Development
View App Services
LABS
Guides

Setting Up a Verifiable Credentials Framework for User Authentication

A technical guide for developers implementing the W3C Verifiable Credentials data model. Learn to issue, hold, and verify digital claims for secure, privacy-preserving authentication.
Chainscore © 2026
introduction
DECENTRALIZED IDENTITY

Introduction to Verifiable Credentials for Authentication

Verifiable Credentials (VCs) provide a portable, user-centric alternative to traditional login systems by shifting trust from centralized databases to cryptographic proofs.

A Verifiable Credential is a tamper-evident digital claim issued by an authority (like a government or university) and cryptographically signed. For authentication, a user presents a VC—such as a proof of age or email ownership—instead of a password. The core components are the issuer, the holder (user), and the verifier (the app). This model, defined by the W3C standard, enables self-sovereign identity where users control their data. Unlike OAuth, where an app queries an external provider, VCs allow the user to present proof directly, reducing reliance on third-party availability.

The technical foundation relies on Decentralized Identifiers (DIDs) and digital signatures. A DID is a unique, user-owned identifier (e.g., did:key:z6Mk...) stored on a blockchain or other decentralized system. The issuer signs the credential with their private key, binding it to the holder's DID. When authenticating, the holder creates a Verifiable Presentation, which includes the VC and a proof (like a signature) demonstrating they control the associated DID. The verifier checks the issuer's signature and the presentation proof without needing to contact the issuer directly, enabling offline verification.

To implement a basic VC flow for authentication, you need a wallet for the user (holder) and a verifier library for your application. For developers, libraries like did-jwt-vc (JavaScript) or vc-js simplify the process. A typical sign-in sequence involves: 1) Your app requests a specific VC type (e.g., an EmailCredential), 2) The user's wallet selects and signs the credential to create a Verifiable Presentation, 3) Your app's backend verifies the presentation's signatures and checks for revocation (often via a status list). This replaces the traditional username/password exchange with a cryptographic handshake.

Key advantages for developers include reduced liability (you don't store sensitive PII), improved user experience (one-click logins across sites), and interoperability through shared standards. However, challenges exist: key management for users, establishing trust in issuers, and handling revocation. Protocols like W3C Verifiable Credentials Data Model v2.0 and DIDComm for secure messaging are evolving to address these. For production, consider integrating with existing issuer networks like trustbloc or using cloud VC services from providers like Microsoft Entra Verified ID.

A practical example is age-gating without collecting birthdates. A user obtains a VC from a trusted issuer stating they are over 18. To access your service, they present this VC. Your backend verifies the issuer's signature (using their public DID) and the credential's validity period. The code snippet below shows a basic verification check using the did-jwt-vc library:

javascript
const { verifyCredential } = require('did-jwt-vc');
const verifiedVC = await verifyCredential(vcJwt, resolver);
if (verifiedVC.verified && verifiedVC.payload.claim.age > 18) {
  // Grant access
}

This demonstrates how verification logic moves from database lookups to local cryptographic checks.

The ecosystem is maturing with Ethereum Attestation Service (EAS) for on-chain credentials, Civic's Passport, and Sign-In with Ethereum (SIWE) extensions. For authentication, the next step is exploring selective disclosure (proving you're over 21 without revealing your birthdate) using zero-knowledge proofs (ZKPs) with tools like Sismo or zkPass. Start by experimenting with the SpruceID SDK or Microsoft's VC samples, and always prioritize user custody of private keys in your design to truly leverage the decentralized benefits of Verifiable Credentials.

prerequisites
PREREQUISITES AND SETUP

Setting Up a Verifiable Credentials Framework for User Authentication

This guide outlines the technical prerequisites and initial setup required to implement a decentralized identity system using Verifiable Credentials (VCs) for user authentication.

Verifiable Credentials (VCs) are a W3C standard for creating tamper-evident, cryptographically secure digital credentials. They enable users to own and control their identity data, presenting claims (like age or membership) to verifiers without relying on a central authority. The core components of a VC framework are the Issuer (who creates the credential), the Holder (the user who stores it), and the Verifier (the service that checks it). For authentication, a user presents a credential, and the verifier checks its cryptographic proof and the issuer's signature against a Decentralized Identifier (DID) documented on a verifiable data registry, typically a blockchain.

Before writing code, you must choose a foundational technology stack. For managing DIDs and their cryptographic keys, consider using a DID method like did:ethr (Ethereum), did:key, or did:web. Libraries such as did-jwt-vc (JavaScript/TypeScript) or veramo (a meta-framework) handle VC creation and verification. You will also need access to a verifiable data registry. For Ethereum-based DIDs, this means connecting to an Ethereum node via a provider like Infura or Alchemy, or using a testnet. Ensure your environment has Node.js (v18+) or your chosen runtime installed.

Start by initializing a project and installing core dependencies. For a Node.js project using Veramo, run npm init -y followed by npm install @veramo/core @veramo/data-store @veramo/did-manager @veramo/credential-ws. Configure an agent with a DID provider and a key management system. The following snippet sets up a Veramo agent with an ethr DID provider for the Sepolia testnet:

javascript
import { createAgent } from '@veramo/core';
import { DIDManager } from '@veramo/did-manager';
import { EthrDIDProvider } from '@veramo/did-provider-ethr';
import { KeyManager } from '@veramo/key-manager';
import { KeyManagementSystem } from '@veramo/kms-local';

const agent = createAgent({
  plugins: [
    new KeyManager({
      store: new MemoryKeyStore(),
      kms: { local: new KeyManagementSystem() },
    }),
    new DIDManager({
      providers: {
        'did:ethr:sepolia': new EthrDIDProvider({
          defaultKms: 'local',
          network: 'sepolia',
          rpcUrl: 'https://sepolia.infura.io/v3/YOUR_API_KEY',
        }),
      },
    }),
  ],
});

With the agent configured, you can create a DID for an issuer. Execute await agent.didManagerCreate({ provider: 'did:ethr:sepolia' }). This generates a new Ethereum key pair, registers the DID on the Sepolia testnet, and stores the private key securely in your configured KMS. The returned DID document contains the public key necessary for verifying signatures. This DID will be used as the issuer field in any Verifiable Credentials you create. Keep the agent's database (or key store) secure, as it holds the private signing keys.

The final prerequisite is designing the credential structure. Define the claims your authentication system will verify, such as isOver18 (boolean) or membershipTier (string). Structure these claims in a JSON object that conforms to the Verifiable Credentials Data Model. You will also need to decide on the proof format; JWT is common for web authentication due to its compact size and library support, while JSON-LD with Linked Data Proofs offers enhanced semantic interoperability. Your verifier's logic must be built to check the specific proof type and the revocation status of the credential, which may involve checking a smart contract or a credential status list.

key-concepts-text
VERIFIABLE CREDENTIALS

Core Concepts: Issuer, Holder, Verifier

A verifiable credentials framework for user authentication is built on a triangular trust model involving three core roles. This guide explains the responsibilities and technical interactions of each.

The Issuer is the authoritative entity that creates and cryptographically signs credentials. In an authentication context, this is typically the service provider (e.g., a university, government agency, or corporate HR system) that attests to a user's attributes. The issuer's core technical tasks are defining a credential schema, binding claims to a user's Decentralized Identifier (DID), and signing the credential with their private key. This creates a tamper-proof, machine-verifiable data package, such as a W3C Verifiable Credential (VC).

The Holder is the user or entity that receives and controls the credential. The holder stores the credential in a digital wallet—often a self-sovereign identity (SSI) wallet—and presents it when required. Crucially, the holder does not simply forward the raw credential. To protect privacy, they create a Verifiable Presentation (VP), which can selectively disclose only the necessary claims (e.g., proving they are over 18 without revealing their birthdate). The holder signs the presentation with their own DID key, proving control over the credential.

The Verifier is the relying party that needs to authenticate the user or check their attributes. This is often a different website or service. The verifier's job is to request a specific credential type, receive the holder's Verifiable Presentation, and perform cryptographic checks. This involves verifying the digital signatures from both the issuer (to confirm credential authenticity) and the holder (to confirm credential possession), and checking that the credential has not been revoked, typically via a revocation registry or status list.

The trust flows from issuer to verifier. A verifier must trust the issuer's DID and its associated public key, often resolved via a DID method like did:ethr or did:key. In code, verification involves libraries like veramo or did-jwt-vc. For example, a Node.js verifier might use agent.verifyPresentation() to validate the signatures and credential status, returning a boolean result. This model eliminates the need for the verifier to directly query the issuer for every authentication, enabling offline verification and user-centric data control.

A practical implementation for web authentication could use Sign-In with Ethereum (SIWE) enhanced with VCs. A user (holder) obtains a "KYC Level 2" VC from an issuer. Later, when logging into a DeFi app (verifier), they present a VP containing this credential. The app's backend verifies the VP's signatures and the issuer's accreditation on-chain, granting access without handling personal data. This shifts the architecture from centralized user databases to a decentralized, cryptographic proof-of-claim system.

schema-design
FOUNDATION

Step 1: Designing Your Credential Schema

A well-defined schema is the blueprint for your verifiable credentials, ensuring data consistency, interoperability, and trust. This step is critical before any code is written.

A credential schema is a formal definition of the data structure for your verifiable credential (VC). It specifies the properties, their data types, and any constraints (like format or required fields). Think of it as the equivalent of a database schema or a TypeScript interface for your user's attestations. Using a standardized schema ensures that any verifier, wallet, or application can understand and process the credential's data correctly, which is essential for interoperability across different systems in the Web3 ecosystem.

For on-chain credential frameworks like Verax or EAS (Ethereum Attestation Service), the schema is registered on-chain, creating a permanent, immutable reference. This registration returns a unique schema UID or hash that is used to link all credentials issued against it. When designing your schema, you must decide on the core claims. For user authentication, common properties include userId (string), authProvider (string), authTimestamp (integer), and levelOfAssurance (integer). Avoid storing sensitive PII directly; use hashes or zero-knowledge proofs where possible.

Here is an example of a JSON schema definition for a basic authentication credential, following the common structure used by platforms like Verax:

json
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "title": "BasicAuthCredential",
  "type": "object",
  "properties": {
    "userId": {
      "type": "string",
      "description": "Unique identifier for the user within the issuing system."
    },
    "authProvider": {
      "type": "string",
      "description": "The authentication method used (e.g., 'google_oauth', 'wallet_signature')."
    },
    "authTimestamp": {
      "type": "integer",
      "description": "Unix timestamp of the authentication event."
    }
  },
  "required": ["userId", "authProvider", "authTimestamp"]
}

This schema defines three required fields. The authTimestamp allows verifiers to check the credential's freshness, a key security consideration.

Before finalizing, consider future-proofing and composability. Will you need to add new fields later? It's often better to create a new schema version than to modify an existing, widely-used one. Also, think about how this credential might be combined with others. A modular design—where a user holds separate credentials for authentication, KYC, and reputation—is more flexible than a single monolithic credential. Reference existing community schemas on platforms like Verax's Attestation Registry or EAS's Schema Explorer for inspiration and best practices.

Once your schema is designed, the next step is to register it on your chosen attestation network. This is typically done via a transaction, which will consume gas. For Ethereum and L2s using EAS, you would call EAS.register(schema) where schema is a string like "(uint256 userId, string authProvider, uint64 authTimestamp)". For Verax on Linea or other chains, you would use their specific SDK or smart contract interface. Keep the registered schema UID secure, as it will be the root identifier for all credentials you issue in the following steps.

issuance-flow
DEVELOPER TUTORIAL

Step 2: Implementing the Credential Issuance Flow

This guide details the technical implementation for issuing W3C Verifiable Credentials (VCs) to users, establishing a secure, portable foundation for authentication.

The issuance flow begins when a user successfully completes an authentication or verification process with your application. At this point, your backend must construct a Verifiable Credential payload. This is a JSON-LD document containing the credential's metadata, the user's claims (e.g., "email": "user@example.com", "kycLevel": "verified"), and a proof section. You will use a Decentralized Identifier (DID) as the issuer, which cryptographically binds you to the credential. For example, using the did:web method, your issuer DID might be did:web:yourdomain.com.

Next, you must cryptographically sign the credential to create a Verifiable Presentation ready for the user. In JavaScript, using the @digitalbazaar/vc and @digitalbazaar/ed25519-signature-2020 libraries, the signing process looks like this:

javascript
const vc = await vc.issue({
  credential: credentialPayload,
  suite: new Ed25519Signature2020({ key: issuerKeyPair }),
  documentLoader: customLoader
});

The resulting signed VC is a standalone, tamper-evident package. The user's wallet or agent (like a browser extension) receives this VC and stores it in their Identity Wallet, a secure local store under their control.

For the credential to be useful in a cross-application context, it must be discoverable and verifiable. This is achieved by publishing your DID Document to the location specified by your DID (e.g., https://yourdomain.com/.well-known/did.json). This public document contains your public key, which verifiers use to check the credential's signature. You should also define a Verifiable Credential Schema on a registry like the Schema.org extension or a blockchain registry to standardize the structure and meaning of your credentials, ensuring interoperability.

presentation-request
VERIFIABLE CREDENTIALS FRAMEWORK

Step 3: Creating a Presentation Request

A presentation request is a structured query that asks a user to share specific verifiable credentials, defining what data is needed and how it should be proven.

A Presentation Request is the formal mechanism for a Verifier (like your application) to ask a user for proof. It's not a simple data dump request; it specifies the exact credentials required, the claims within them, and the cryptographic proofs needed for verification. This request is typically encoded as a JSON object following the W3C Presentation Exchange specification or a similar standard. It acts as the query that initiates the user's consent flow, prompting their wallet to select and present the appropriate credentials.

The core of the request is the presentation_definition. This JSON structure defines the input descriptors—the specific pieces of data you need. For a user authentication scenario, you might request a VerifiedEmailCredential with a claim for emailAddress. You can also specify constraints, such as requiring the credential to be issued by a trusted Decentralized Identifier (DID) like did:web:your-auth-provider.com. This ensures you only accept credentials from your designated identity provider.

Beyond the data, the request must specify the proof requirements. This tells the user's wallet what type of cryptographic signature to generate. For most frameworks, you will require a Linked Data Proof, such as a Ed25519Signature2020 or a JsonWebSignature2020. The request will ask the holder to sign the presentation, creating a Verifiable Presentation. This signature cryptographically binds the presented credentials to that specific request, preventing replay attacks.

Here is a simplified example of a presentation_definition for an email login:

json
{
  "id": "auth_presentation_request_1",
  "input_descriptors": [{
    "id": "email_credential",
    "purpose": "We need to verify your registered email address.",
    "constraints": {
      "fields": [{
        "path": ["$.credentialSubject.emailAddress"],
        "filter": {
          "type": "string"
        }
      }],
      "limit_disclosure": "required"
    }
  }]
}

This definition asks for a credential containing a string claim at the path credentialSubject.emailAddress and mandates Selective Disclosure (limit_disclosure), allowing the user to share only the email, not the entire credential.

Finally, you deliver this request to the user. The standard method is to encode it within a Deep Link or a QR Code that launches the user's credential wallet. The wallet parses the presentation_definition, lets the user select which credentials satisfy it, and then constructs and signs the Verifiable Presentation. Your application receives this presentation in the callback URL or via a direct response, which you will verify in the next step.

verification-logic
IMPLEMENTATION

Step 4: Verifying a Credential Presentation

This step details the server-side logic to cryptographically verify a credential presentation submitted by a user, ensuring the data is authentic, unaltered, and issued by a trusted source.

After a user submits a Verifiable Presentation (VP)—a cryptographically signed package containing one or more Verifiable Credentials (VCs)—your authentication server must verify its validity. This process involves several distinct checks: verifying the VP's own digital signature, checking the proofs on each contained VC, validating the credential's schema and issuer, and ensuring the presentation meets your specific policy requirements (e.g., credential type, issuance date). Libraries like veramo or did-jwt-vc provide the core verification functions, but you must orchestrate the logic.

The first technical step is to verify the presentation's proof. A VP is typically a JSON-LD or JWT structure with a proof object. For a JWT VP, you decode and verify the JWT signature using the presenter's Decentralized Identifier (DID) and its associated public key, which is resolved from its DID Document. This confirms the user (the holder) indeed created and sent this presentation. For JSON-LD, you verify the linked data signature using the appropriate cryptographic suite.

Next, you verify each Verifiable Credential within the presentation. For each VC, you must: 1) Verify its cryptographic proof (e.g., a JWT signature or a linked data proof) using the issuer's DID and public key. 2) Check that the credential has not been revoked by querying the issuer's revocation registry (like a smart contract or a credential status list). 3) Validate that the credential's structure conforms to the expected schema (e.g., a UniversityDegreeCredential schema from a known schema registry).

Your application must also enforce business logic and policy. This includes checking credential subjects match the presenter's DID, verifying issuance and expiration dates are valid, and confirming the credential type (type field) is one you accept for authentication. For example, you may only accept a GovernmentIDCredential from a specific list of trusted issuer DIDs for a KYC process. This policy check is where you bridge the trust from the cryptographic layer to your application's requirements.

Here is a simplified Node.js example using the veramo agent to verify a JWT Verifiable Presentation and the credential within it:

javascript
const result = await agent.verifyPresentation({
  presentation: {
    holder: 'did:ethr:0x123...',
    verifiableCredential: [receivedJwtVc],
    proof: { ... }
  },
  challenge: 'your-session-challenge-123', // Must match the one sent!
  domain: 'your-app.com'
});

if (result.verified) {
  // Check policy against result.verifiableCredential[0].credentialSubject
  const claims = result.verifiableCredential[0].credentialSubject;
  if (claims.degreeType === 'Bachelor' && claims.issuer === 'did:web:university.edu') {
    // Authentication successful
  }
}

Note the critical challenge parameter, which prevents replay attacks by ensuring the presentation was created for this specific authentication session.

Finally, handle verification failures gracefully. Distinguish between cryptographic failures (invalid signature, expired credential) and policy failures (untrusted issuer, incorrect credential type). Log these events for security auditing and provide clear, non-leaky error messages to the user (e.g., "Credential expired" vs. "Signature invalid"). Successful verification means you can trust the claims within the credential (credentialSubject) and create an authenticated session for the user based on those verified attributes, completing the passwordless login flow.

CORE SPECIFICATION

Credential Format Comparison: JWT-VC vs. JSON-LD

A technical comparison of the two primary W3C Verifiable Credential data formats, highlighting differences in complexity, proof mechanisms, and interoperability.

Feature / MetricJWT-VC (RFC 7519)JSON-LD (Data Integrity)

Core Standard

RFC 7519 (JWT) + VC-JWT

W3C JSON-LD 1.1 + Data Integrity

Proof Mechanism

JWS (JSON Web Signature)

Linked Data Proofs (Ed25519Signature2020, etc.)

Cryptographic Agility

Limited (JWA alg parameter)

High (modular proof suites)

Linked Data Compliance

Default Serialization

Compact JWT string

JSON-LD document

Schema Validation

Custom JSON Schema

JSON-LD Context + SHACL

Selective Disclosure

Requires SD-JWT extension

Native via BBS+ signatures

Average Proof Size

~1-2 KB

~2-5 KB

Library Maturity

High (many JWT libs)

Moderate (specialized libs required)

DEVELOPER FAQ

Frequently Asked Questions

Common technical questions and troubleshooting for implementing a verifiable credentials framework for user authentication.

Verifiable Credentials (VCs) are a W3C standard for creating tamper-evident, cryptographically signed digital credentials. They enable self-sovereign identity (SSI), where users control their own data. For authentication, the flow involves three roles:

  • Issuer: A trusted entity (like a government or university) signs a VC attesting to a user's attribute (e.g., "over 18").
  • Holder: The user stores the VC in a digital wallet (e.g., SpruceID's Credible).
  • Verifier: An application requests proof. The holder presents a Verifiable Presentation (VP), a package containing the relevant VC(s).

The verifier checks the cryptographic signatures against the issuer's public Decentralized Identifier (DID) and the credential's status (e.g., on a revocation registry). This replaces traditional username/password with cryptographic proof of attributes, enabling selective disclosure without centralized databases.

use-cases
VERIFIABLE CREDENTIALS

Authentication Use Cases

Verifiable Credentials (VCs) enable decentralized, user-centric identity. This framework allows users to prove claims—like KYC status or professional accreditation—without revealing underlying data, shifting control from centralized issuers to the individual.

01

Decentralized KYC and AML Compliance

Financial institutions can issue Verifiable Credentials for completed Know Your Customer (KYC) checks. Users can then present this credential to multiple DeFi protocols or exchanges without repeating the process. This reduces friction and personal data exposure while maintaining regulatory compliance. Key components include:

  • Selective Disclosure: Users prove they are over 18 or accredited without sharing their full ID.
  • Revocation Registries: Issuers can invalidate credentials if a user's status changes.
  • Real-world example: The Travel Rule solution using VCs for cross-border crypto transactions.
02

Sybil-Resistant Airdrops and Governance

Protocols use VCs to distribute tokens and voting power to unique humans, preventing bot farms from capturing rewards. A user proves their "uniqueness" with a credential from a trusted issuer, like a proof-of-personhood service (e.g., Worldcoin, BrightID). This enables:

  • Fair Launches: Airdrops weighted towards genuine community members.
  • One-person-one-vote: Governance systems resistant to whale manipulation via sockpuppet accounts.
  • Implementation: Integrate with Disco.xyz or Veramo to verify credentials on-chain before distributing tokens.
04

Gated Access for Content and Services

Creators and DAOs can gate access to content, Discord roles, or premium features based on verifiable user attributes. Instead of checking a wallet's NFT balance on-chain for every request, a user can present a VC issued upon verification. This is more private and gas-efficient. Use cases:

  • Token-Gated Articles: Show a VC proving you hold a specific NFT to read content.
  • DAO Role Management: Automatically assign Discord 'member' role based on a governance token credential.
  • Tools: Use Disco.xyz for issuing credentials and Collab.Land or Guild.xyz for gating.
conclusion
IMPLEMENTATION GUIDE

Conclusion and Next Steps

You have now implemented the core components of a verifiable credentials framework for user authentication. This section summarizes the key takeaways and outlines advanced topics for production deployment.

Building a verifiable credentials (VC) framework shifts authentication from centralized databases to user-held, cryptographically verifiable proofs. The core workflow you've implemented involves three parties: the issuer (who creates and signs credentials), the holder (the user who stores them in a digital wallet), and the verifier (your application that requests and validates proofs). By using standards like W3C Verifiable Credentials and Decentralized Identifiers (DIDs), you ensure interoperability across different systems and avoid vendor lock-in. The primary security benefit is that your application no longer needs to store sensitive user data; it only needs to verify the cryptographic signatures on the presented credentials.

For a production-ready system, several critical next steps are required. First, implement proper key management and rotation for your issuer DID. Private keys should be stored in a hardware security module (HSM) or a cloud KMS like AWS KMS or Google Cloud KMS. Second, establish a revocation mechanism. While a simple on-chain registry was shown, consider more scalable solutions like revocation lists (2020) or the StatusList2021 specification. Third, design a robust holder wallet integration. Provide clear SDKs or libraries for your users, supporting both browser-based (e.g., MetaMask snaps, browser extensions) and mobile (e.g., native wallet apps) environments.

To enhance user experience and security, explore advanced presentation protocols. The W3C Verifiable Presentation standard allows holders to combine multiple credentials into a single, verifiable package. Implement Selective Disclosure using zero-knowledge proofs (ZKPs) with technologies like BBS+ signatures or zk-SNARKs circuits, enabling users to prove they are over 18 without revealing their exact birthdate. Furthermore, integrate with trust frameworks and credential schemas published to verifiable data registries (VDRs) like the ION network or Cheqd to ensure your credentials are recognized by other verifiers in the ecosystem. Always conduct thorough security audits, particularly of any smart contracts used for registry or revocation, before mainnet deployment.