Aprenda a construir um Verifier de credenciais digitais do zero usando Next.js, OpenID4VP e ISO mDoc. Este guia de desenvolvedor passo a passo mostra como criar um Verifier que pode solicitar, receber e validar carteiras de motorista móveis e outras crede
Amine
Created: August 20, 2025
Updated: August 21, 2025
See the original blog version in English here.
Provar identidades online é um desafio constante, que nos leva a depender de senhas e a compartilhar documentos confidenciais por canais inseguros. Para as empresas, isso tornou a verificação de identidade um processo lento, caro e suscetível a fraudes. As Credenciais Digitais oferecem uma nova abordagem, colocando os usuários de volta no controle de seus dados. Elas são o equivalente digital de uma carteira física, contendo desde uma carteira de motorista até um diploma universitário, mas com os benefícios adicionais de serem criptograficamente seguras, preservarem a privacidade e serem instantaneamente verificáveis.
Este guia oferece aos desenvolvedores um tutorial prático e passo a passo para construir um Verifier para Credenciais Digitais. Embora os padrões existam, há pouca orientação sobre como implementá-los. Este tutorial preenche essa lacuna, mostrando como construir um Verifier usando a API de Credenciais Digitais nativa do navegador, o OpenID4VP para o protocolo de apresentação e o ISO mDoc (por exemplo, carteira de motorista móvel) como formato da credencial.
O resultado final será uma aplicação Next.js simples, mas funcional, que pode solicitar, receber e verificar uma Credencial Digital de uma carteira móvel compatível.
Veja um rápido vislumbre da aplicação final em ação. O processo envolve quatro etapas principais:
Etapa 1: Página Inicial O usuário chega à página inicial e clica em "Verificar com Identidade Digital" para iniciar o processo.
Etapa 2: Prompt de Confiança O navegador solicita a confiança do usuário. O usuário clica em "Continuar" para prosseguir.
Etapa 3: Escaneamento do QR Code Um QR code é exibido, que o usuário escaneia com sua aplicação de carteira compatível.
Etapa 4: Credencial Decodificada Após a verificação bem-sucedida, a aplicação exibe os dados da credencial decodificada.
Recent Articles
📝
Como construir um Verifier de Credenciais Digitais (Guia para Desenvolvedores)
📝
Como construir um Emissor de Credenciais Digitais (Guia para Desenvolvedores)
📖
Chave Residente WebAuthn: Credenciais Detectáveis como Passkeys
🔑
Guia Técnico: Acesso com Crachá Físico e Passkeys
🔑
Tornando o MFA Obrigatório e Adotando Passkeys: Melhores Práticas
A magia por trás das credenciais digitais está em um modelo de "triângulo de confiança" simples, mas poderoso, envolvendo três participantes principais:
Quando um usuário quer acessar um serviço, ele apresenta a credencial de sua carteira. O Verifier pode então verificar instantaneamente sua autenticidade sem precisar contatar o Issuer original diretamente.
Para que este ecossistema de identidade descentralizada prospere, o papel do Verifier é absolutamente crítico. Eles são os guardiões desta nova infraestrutura de confiança, aqueles que consomem as credenciais e as tornam úteis no mundo real. Como o diagrama abaixo ilustra, um Verifier completa o triângulo de confiança ao solicitar, receber e validar uma credencial do Holder.
Se você é um desenvolvedor, construir um serviço para realizar essa verificação é uma habilidade fundamental para a próxima geração de aplicações seguras e centradas no usuário. Este guia foi projetado para orientá-lo exatamente nesse processo. Cobriremos tudo o que você precisa saber para construir seu próprio Verifier de credenciais verificáveis, desde os conceitos e padrões fundamentais até os detalhes da implementação passo a passo da validação de assinaturas e verificação do status da credencial.
Quer pular direto para o final? Você pode encontrar o projeto completo deste tutorial no GitHub. Sinta-se à vontade para cloná-lo e testá-lo você mesmo: https://github.com/corbado/digital-credentials-example
Vamos começar.
Antes de começar, certifique-se de que você tem:
Agora vamos detalhar cada um desses pré-requisitos, começando pelos padrões e protocolos que sustentam este Verifier baseado em mdoc.
Nosso Verifier foi construído para o seguinte:
Padrão / Protocolo | Descrição |
---|---|
W3C VC | O Modelo de Dados de Credenciais Verificáveis do W3C. Ele define a estrutura padrão para credenciais digitais, incluindo claims, metadados e provas. |
SD-JWT | Divulgação Seletiva para JWTs. Um formato para VCs baseado em JSON Web Tokens que permite aos Holders divulgar seletivamente apenas claims específicas de uma credencial, aumentando a privacidade. |
ISO mDoc | ISO/IEC 18013-5. O padrão internacional para Carteiras de Motorista móveis (mDLs) e outros IDs móveis, definindo estruturas de dados e protocolos de comunicação para uso online e offline. |
OpenID4VP | OpenID for Verifiable Presentations. Um protocolo de apresentação interoperável construído sobre OAuth 2.0. Ele define como um Verifier solicita credenciais e como a wallet do Holder as apresenta. |
Para este tutorial, usamos especificamente:
Nota sobre o Escopo: Embora introduzamos brevemente W3C VC e SD-JWT para fornecer um contexto mais amplo, este tutorial implementa exclusivamente credenciais ISO mDoc via OpenID4VP. VCs baseadas em W3C estão fora do escopo deste exemplo.
O padrão ISO/IEC 18013-5 mDoc define a estrutura e a codificação para documentos móveis, como carteiras de motorista móveis (mDLs). As credenciais mDoc são codificadas em CBOR, assinadas criptograficamente e podem ser apresentadas digitalmente para verificação. Nosso Verifier se concentrará em decodificar e validar essas credenciais mdoc.
O OpenID4VP é um protocolo interoperável para solicitar e apresentar credenciais digitais, construído sobre OAuth 2.0 e OpenID Connect. Nesta implementação, o OpenID4VP é usado para:
Agora que temos um entendimento claro dos padrões e protocolos, precisamos escolher o tech stack certo para construir nosso Verifier. Nossas escolhas são projetadas para robustez, experiência do desenvolvedor e compatibilidade com o ecossistema web moderno.
Usaremos TypeScript tanto para nosso código de frontend quanto de backend. Como um superset do JavaScript, ele adiciona tipagem estática, o que ajuda a capturar erros mais cedo, melhora a qualidade do código e torna aplicações complexas mais fáceis de gerenciar. Em um contexto sensível à segurança como a verificação de credenciais, a segurança de tipos é uma grande vantagem.
Next.js é nosso framework de escolha porque oferece uma experiência integrada e fluida para construir aplicações full-stack.
redirect_uri
para receber e verificar com segurança a
resposta final da CMWallet.Nossa implementação depende de um conjunto específico de bibliotecas para o frontend e o backend:
Nota sobre openid-client
: Verifiers mais avançados e prontos para produção podem
usar a biblioteca openid-client
para lidar diretamente com o protocolo OpenID4VP no
backend, habilitando recursos como um redirect_uri
dinâmico. Em um fluxo OpenID4VP
orientado pelo servidor com um redirect_uri
, o openid-client
seria usado para
analisar e validar respostas vp_token
diretamente. Para este tutorial, estamos usando
um fluxo mais simples, mediado pelo navegador, que não o exige, tornando o processo mais
fácil de entender.
Este tech stack garante uma implementação de Verifier robusta, com segurança de tipos e escalável, focada na API de Credenciais Digitais do navegador e no formato de credencial ISO mDoc.
Para testar seu Verifier, você precisa de uma carteira móvel que possa interagir com a API de Credenciais Digitais do navegador.
Usaremos a CMWallet, uma robusta wallet de teste compatível com OpenID4VP para Android.
Como Instalar a CMWallet (Android):
Nota: Instale arquivos APK apenas de fontes confiáveis. O link fornecido é do repositório oficial do projeto.
Antes de mergulharmos na implementação, é essencial entender os conceitos criptográficos que sustentam as credenciais verificáveis. É isso que as torna "verificáveis" e confiáveis.
No seu cerne, uma Credencial Verificável é um conjunto de declarações (como nome, data de nascimento, etc.) que foi assinado digitalmente por um Issuer. Uma assinatura digital oferece duas garantias críticas:
As assinaturas digitais são criadas usando criptografia de chave pública/privada (também chamada de criptografia assimétrica). Veja como funciona em nosso contexto:
Nota sobre DIDs: Neste tutorial, não resolvemos as chaves do Issuer via DIDs. Em produção, os Issuers normalmente exporiam chaves públicas via DIDs ou outros endpoints autoritativos, que o Verifier usaria para validação criptográfica.
Credenciais Verificáveis são frequentemente formatadas como JSON Web Tokens (JWTs). Um
JWT é uma forma compacta e segura para URLs de representar declarações a serem
transferidas entre duas partes. Um JWT assinado (também conhecido como JWS) tem três
partes separadas por pontos (.
):
alg
).vc
), incluindo o
issuer
, credentialSubject
, etc.// Exemplo de uma estrutura JWT [Header].[Payload].[Signature]
Nota: Credenciais Verificáveis baseadas em JWT estão fora do escopo deste post de blog. Esta implementação foca em credenciais ISO mDoc e OpenID4VP, não em Credenciais Verificáveis W3C ou credenciais baseadas em JWT.
Não é suficiente para um Verifier saber que uma credencial é válida; ele também precisa saber que a pessoa que apresenta a credencial é o titular legítimo. Isso impede que alguém use uma credencial roubada.
Isso é resolvido usando uma Apresentação Verificável (VP). Uma VP é um invólucro em torno de uma ou mais VCs que é assinado pelo próprio Holder.
O fluxo é o seguinte:
Nosso Verifier deve então realizar duas verificações de assinatura separadas:
Esta verificação em dois níveis garante tanto a autenticidade da credencial quanto a identidade da pessoa que a apresenta, criando um modelo de confiança robusto e seguro.
Nota: O conceito de Apresentações Verificáveis, conforme definido no ecossistema W3C
VC, está fora do escopo deste post. O termo
Apresentação Verificável aqui se refere à resposta
vp_token
do OpenID4VP, que se comporta de maneira semelhante a uma VP do W3C, mas é
baseada na semântica do ISO mDoc em vez do modelo de assinatura
JSON-LD do W3C. Este guia foca em credenciais ISO mDoc e OpenID4VP,
não em Apresentações Verificáveis W3C ou sua validação de assinatura.
Nossa arquitetura de Verifier usa a API de Credenciais Digitais nativa do navegador como um intermediário seguro para conectar nossa aplicação web com a CMWallet móvel do usuário. Essa abordagem simplifica o fluxo, permitindo que o navegador lide com a exibição do QR code nativo e a comunicação com a wallet.
navigator.credentials.get()
do navegador, receber o resultado e encaminhá-lo ao nosso
backend para verificação.openid4vp
e gera nativamente um QR code. Em
seguida, aguarda a wallet retornar uma resposta.Aqui está um diagrama de sequência ilustrando o fluxo completo e preciso:
O Fluxo Explicado:
/api/verify/start
),
que gera um objeto de solicitação contendo a query e um nonce, e então o retorna.navigator.credentials.get()
com o
objeto de solicitação.openid4vp
e exibe
nativamente um QR code. A promise .get()
agora está pendente.Nota: Este fluxo de QR code ocorre em navegadores de desktop. Em navegadores móveis
(Android Chrome com a flag experimental ativada), o navegador pode se comunicar
diretamente com wallets compatíveis no mesmo dispositivo, eliminando a necessidade de
escanear o QR code. Para habilitar esse recurso no Android Chrome, navegue para
chrome://flags#web-identity-digital-credentials
e defina a flag como "Enabled".
.get()
original
no frontend finalmente é resolvida, entregando o payload da apresentação./api/verify/finish
do nosso backend. O backend valida o nonce e a
credencial.Agora que temos uma compreensão sólida dos padrões, protocolos e do fluxo arquitetural, podemos começar a construir nosso Verifier.
Acompanhe ou Use o Código Final
Agora vamos passar pela configuração e implementação do código passo a passo. Se preferir pular direto para o produto final, você pode clonar o projeto completo do nosso repositório no GitHub e executá-lo localmente.
git clone https://github.com/corbado/digital-credentials-example.git
Primeiro, vamos inicializar um novo projeto Next.js, instalar as dependências necessárias e iniciar nosso banco de dados.
Abra seu terminal, navegue até o diretório onde deseja criar seu projeto e execute o seguinte comando. Estamos usando o App Router, TypeScript e Tailwind CSS para este projeto.
npx create-next-app@latest . --ts --eslint --tailwind --app --src-dir --import-alias "@/*" --use-npm
Este comando cria o esqueleto de uma nova aplicação Next.js em seu diretório atual.
Em seguida, precisamos instalar as bibliotecas que lidarão com a decodificação CBOR, conexões com o banco de dados e geração de UUID.
npm install cbor-web mysql2 uuid @types/uuid
Este comando instala:
cbor-web
: Para decodificar o payload da credencial mdoc.mysql2
: O cliente MySQL para nosso banco de dados.uuid
: Para gerar strings de desafio únicas.@types/uuid
: Tipos TypeScript para a biblioteca uuid
.Nosso backend requer um banco de dados MySQL para armazenar dados de sessão OIDC,
garantindo que cada fluxo de verificação seja seguro e com estado. Incluímos um arquivo
docker-compose.yml
para facilitar isso.
Se você clonou o repositório, pode simplesmente executar docker-compose up -d
. Se
estiver construindo do zero, crie um arquivo chamado docker-compose.yml
com o seguinte
conteúdo:
services: mysql: image: mysql:8.0 restart: always environment: MYSQL_ROOT_PASSWORD: rootpassword MYSQL_DATABASE: digital_credentials MYSQL_USER: app_user MYSQL_PASSWORD: app_password ports: - "3306:3306" volumes: - mysql_data:/var/lib/mysql - ./sql/init.sql:/docker-entrypoint-initdb.d/init.sql healthcheck: test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] timeout: 20s retries: 10 volumes: mysql_data:
Esta configuração do Docker Compose também requer um script de inicialização SQL. Crie um
diretório chamado sql
e, dentro dele, um arquivo chamado init.sql
com o seguinte
conteúdo para configurar as tabelas necessárias:
-- Create database if not exists CREATE DATABASE IF NOT EXISTS digital_credentials; USE digital_credentials; -- Table for storing challenges CREATE TABLE IF NOT EXISTS challenges ( id VARCHAR(36) PRIMARY KEY, challenge VARCHAR(255) NOT NULL UNIQUE, expires_at TIMESTAMP NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, used BOOLEAN DEFAULT FALSE, INDEX idx_challenge (challenge), INDEX idx_expires_at (expires_at) ); -- Table for storing verification sessions CREATE TABLE IF NOT EXISTS verification_sessions ( id VARCHAR(36) PRIMARY KEY, challenge_id VARCHAR(36), status ENUM('pending', 'verified', 'failed', 'expired') DEFAULT 'pending', presentation_data JSON, verified_at TIMESTAMP NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, FOREIGN KEY (challenge_id) REFERENCES challenges(id) ON DELETE CASCADE, INDEX idx_challenge_id (challenge_id), INDEX idx_status (status) ); -- Table for storing verified credentials data (optional) CREATE TABLE IF NOT EXISTS verified_credentials ( id VARCHAR(36) PRIMARY KEY, session_id VARCHAR(36), credential_type VARCHAR(255), issuer VARCHAR(255), subject VARCHAR(255), claims JSON, verified_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (session_id) REFERENCES verification_sessions(id) ON DELETE CASCADE, INDEX idx_session_id (session_id), INDEX idx_credential_type (credential_type) );
Com ambos os arquivos no lugar, abra seu terminal na raiz do projeto e execute:
docker-compose up -d
Este comando iniciará um contêiner MySQL em segundo plano.
Nossa aplicação Next.js está estruturada para separar as responsabilidades entre o frontend e o backend, embora façam parte do mesmo projeto.
src/app/page.tsx
): Uma única página React que
inicia o fluxo de verificação e exibe o resultado. Ela interage com a API de Credenciais
Digitais do navegador.src/app/api/verify/...
):
start/route.ts
: Gera a solicitação OpenID4VP e um nonce de segurança.finish/route.ts
: Recebe a apresentação da wallet (via navegador), valida o nonce e
decodifica a credencial.src/lib/
):
database.ts
: Gerencia todas as interações com o banco de dados (criação de
desafios, verificação de sessões).crypto.ts
: Lida com a decodificação da credencial mDoc baseada em CBOR.Aqui está um diagrama ilustrando a arquitetura interna:
Nosso frontend é intencionalmente leve. Sua principal responsabilidade é atuar como o gatilho voltado para o usuário para o fluxo de verificação e se comunicar tanto com nosso backend quanto com as capacidades nativas de manipulação de credenciais do navegador. Ele não contém nenhuma lógica de protocolo complexa; tudo isso é delegado.
Especificamente, o frontend lidará com o seguinte:
/api/verify/start
e recebe um
payload JSON estruturado (protocol
, request
, state
) descrevendo exatamente o que a
wallet deve apresentar.navigator.credentials.get()
, que renderiza um QR code nativo e aguarda a resposta da
wallet./api/verify/finish
em uma solicitação POST para a validação final do lado do servidor.A lógica principal está na função startVerification
:
// src/app/page.tsx const startVerification = async () => { setLoading(true); setVerificationResult(null); try { // 1. Verifica se o navegador suporta a API if (!navigator.credentials?.get) { throw new Error("O navegador não suporta a Credential API."); } // 2. Pede ao nosso backend um objeto de solicitação const res = await fetch("/api/verify/start"); const { protocol, request } = await res.json(); // 3. Entrega esse objeto ao navegador – isso aciona o QR code nativo const credential = await (navigator.credentials as any).get({ mediation: "required", digital: { requests: [ { protocol, // "openid4vp" data: request, // contém dcql_query, nonce, etc. }, ], }, }); // 4. Encaminha a resposta da wallet (do navegador) para nosso endpoint de finalização para verificações no lado do servidor const verifyRes = await fetch("/api/verify/finish", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(credential), }); const result = await verifyRes.json(); if (verifyRes.ok && result.verified) { setVerificationResult(`Sucesso: ${result.message}`); } else { throw new Error(result.message || "Falha na verificação."); } } catch (err) { setVerificationResult(`Erro: ${(err as Error).message}`); } finally { setLoading(false); } };
Esta função mostra as quatro etapas principais da lógica do frontend: verificar o suporte da API, buscar a solicitação do backend, chamar a API do navegador e enviar o resultado de volta para verificação. O restante do arquivo é boilerplate padrão do React para gerenciamento de estado e renderização da UI, que você pode ver no repositório do GitHub.
digital
e mediation: 'required'
?#Você pode notar que nossa chamada para navigator.credentials.get()
parece diferente de
exemplos mais simples. Isso ocorre porque estamos aderindo estritamente à
especificação oficial da API de Credenciais Digitais do W3C.
Membro digital
: A especificação exige que todas as solicitações de credenciais
digitais estejam aninhadas dentro de um objeto digital
. Isso fornece um namespace
claro e padronizado для esta API, distinguindo-a de outros tipos de credenciais (como
password
ou federated
) e permitindo extensões futuras sem conflitos.
mediation: 'required'
: Esta opção é um recurso crucial de segurança e experiência
do usuário. Ela impõe que o usuário deva interagir ativamente com um prompt (por
exemplo, uma verificação biométrica, inserção de PIN ou uma tela de consentimento) para
aprovar a solicitação da credencial. Sem isso, um site poderia potencialmente tentar
acessar credenciais silenciosamente em segundo plano, o que representa um risco
significativo à privacidade. Ao exigir a mediação, garantimos que o usuário esteja
sempre no controle e dê consentimento explícito para cada transação.
Com a UI do React no lugar, agora precisamos de duas rotas de API que fazem o trabalho pesado no servidor:
/api/verify/start
– constrói uma solicitação OpenID4VP, persiste um desafio de
uso único no MySQL e entrega tudo de volta ao navegador./api/verify/finish
– recebe a resposta da wallet, valida o desafio, verifica e
decodifica a credencial e, finalmente, retorna um resultado JSON conciso para a UI./api/verify/start
: Gerar a Solicitação OpenID4VP#// src/app/api/verify/start/route.ts import { NextResponse } from "next/server"; import { v4 as uuidv4 } from "uuid"; import { createChallenge, cleanupExpiredChallenges } from "@/lib/database"; export async function GET() { // 1️⃣ Cria um nonce (desafio) aleatório e de curta duração const challenge = uuidv4(); const challengeId = uuidv4(); const expiresAt = new Date(Date.now() + 5 * 60 * 1000); await createChallenge(challengeId, challenge, expiresAt); cleanupExpiredChallenges().catch(console.error); // 2️⃣ Constrói uma query DCQL que descreve *o que* queremos const dcqlQuery = { credentials: [ { id: "cred1", format: "mso_mdoc", meta: { doctype_value: "eu.europa.ec.eudi.pid.1" }, claims: [ { path: ["eu.europa.ec.eudi.pid.1", "family_name"] }, { path: ["eu.europa.ec.eudi.pid.1", "given_name"] }, { path: ["eu.europa.ec.eudi.pid.1", "birth_date"] }, ], }, ], }; // 3️⃣ Retorna um objeto que o navegador pode passar para navigator.credentials.get() return NextResponse.json({ protocol: "openid4vp", // informa ao navegador qual protocolo de wallet usar request: { dcql_query: dcqlQuery, // O QUE apresentar nonce: challenge, // anti-replay response_type: "vp_token", response_mode: "dc_api", // a wallet fará POST diretamente para /finish }, state: { credential_type: "mso_mdoc", // mantido para verificações posteriores nonce: challenge, challenge_id: challengeId, }, }); }
Parâmetros chave
• nonce
– desafio criptográfico que vincula a
solicitação e a resposta (previne replay attacks). • dcql_query
– Um objeto que
descreve as claims exatas que precisamos. Para este guia, usamos uma estrutura
dcql_query
inspirada em rascunhos recentes da Digital Credential Query Language,
embora este ainda não seja um padrão finalizado. • state
– JSON arbitrário
retornado pela wallet para que possamos localizar o registro no banco de dados.
O arquivo src/lib/database.ts
encapsula as operações básicas do MySQL para desafios e
sessões de verificação (inserir, ler, marcar como usado). Manter essa lógica em um único
módulo facilita a troca do armazenamento de dados posteriormente.
/api/verify/finish
: Validar e Decodificar a Apresentação#// src/app/api/verify/finish/route.ts import { NextResponse, NextRequest } from "next/server"; import { v4 as uuidv4 } from "uuid"; import { getChallenge, markChallengeAsUsed, createVerificationSession, updateVerificationSession, } from "@/lib/database"; import { decodeDigitalCredential, decodeAllNamespaces } from "@/lib/crypto"; export async function POST(request: NextRequest) { const body = await request.json(); // 1️⃣ Extrai as partes da apresentação verificável const vpTokenMap = body.vp_token ?? body.data?.vp_token; const state = body.state; const mdocToken = vpTokenMap?.cred1; // pedimos por este ID na dcqlQuery if (!vpTokenMap || !state || !mdocToken) { return NextResponse.json( { verified: false, message: "Resposta malformada" }, { status: 400 }, ); } // 2️⃣ Validação do desafio de uso único const stored = await getChallenge(state.nonce); if (!stored) { return NextResponse.json( { verified: false, message: "Desafio inválido ou expirado" }, { status: 400 }, ); } const sessionId = uuidv4(); await createVerificationSession(sessionId, stored.id); // 3️⃣ Verificações criptográficas (pseudo) – substitua por validação mDL real em produção // Em uma aplicação real, você usaria uma biblioteca dedicada para realizar a validação // criptográfica completa da assinatura mdoc contra a chave pública do issuer. const isValid = mdocToken.length > 0; if (!isValid) { await updateVerificationSession(sessionId, "failed", { reason: "falha na validação do mdoc", }); return NextResponse.json( { verified: false, message: "Falha na validação da credencial" }, { status: 400 }, ); } // 4️⃣ Decodifica o payload da mobile-DL (mdoc) para JSON legível const decoded = await decodeDigitalCredential(mdocToken); const readable = decodeAllNamespaces(decoded)["eu.europa.ec.eudi.pid.1"]; await markChallengeAsUsed(state.nonce); await updateVerificationSession(sessionId, "verified", { readable }); return NextResponse.json({ verified: true, message: "Credencial mdoc verificada com sucesso!", credentialData: readable, sessionId, }); }
Campos importantes na resposta da wallet
• vp_token
– mapa que contém cada credencial que a wallet retorna. Para nossa
demonstração, pegamos vp_token.cred1
. • state
– eco do blob que fornecemos em
/start
; contém o nonce
para que possamos procurar o registro no BD. •
mdocToken
– uma estrutura CBOR codificada em Base64URL que representa o ISO mDoc.
Quando o Verifier recebe uma credencial mdoc do navegador, ela é uma string Base64URL
contendo dados binários codificados em CBOR. Para extrair as claims reais, o endpoint
finish
realiza um processo de decodificação em várias etapas usando funções auxiliares
de src/lib/crypto.ts
.
A função decodeDigitalCredential
lida com a conversão da string codificada para um
objeto utilizável:
// src/lib/crypto.ts export async function decodeDigitalCredential(encodedCredential: string) { // 1. Converte Base64URL para Base64 padrão const base64UrlToBase64 = (input: string) => { let base64 = input.replace(/-/g, "+").replace(/_/g, "/"); const pad = base64.length % 4; if (pad) base64 += "=".repeat(4 - pad); return base64; }; const base64 = base64UrlToBase64(encodedCredential); // 2. Decodifica Base64 para binário const binaryString = atob(base64); const byteArray = Uint8Array.from(binaryString, (char) => char.charCodeAt(0)); // 3. Decodifica CBOR const decoded = await cbor.decodeFirst(byteArray); return decoded; }
cbor-web
para decodificar os dados binários
em um objeto JavaScript estruturado.A função decodeAllNamespaces
processa ainda mais o objeto CBOR decodificado para extrair
as claims reais dos namespaces relevantes:
// src/lib/crypto.ts export function decodeAllNamespaces(jsonObj) { const decoded = {}; try { jsonObj.documents.forEach((doc, idx) => { // 1) issuerSigned.nameSpaces: const issuerNS = doc.issuerSigned?.nameSpaces || {}; Object.entries(issuerNS).forEach(([nsName, entries]) => { if (!decoded[nsName]) decoded[nsName] = {}; (entries as any[]).forEach((entry) => { const bytes = Uint8Array.from(entry.value); const decodedEntry = cbor.decodeFirstSync(bytes); Object.assign(decoded[nsName], decodedEntry); }); }); // 2) deviceSigned.nameSpaces (se presente): const deviceNS = doc.deviceSigned?.nameSpaces; if (deviceNS?.value?.data) { const bytes = Uint8Array.from(deviceNS.value); decoded[`deviceSigned_ns_${idx}`] = cbor.decodeFirstSync(bytes); } }); } catch (e) { console.error(e); } return decoded; }
eu.europa.ec.eudi.pid.1
) para extrair os
valores reais das claims (como nome, data de nascimento, etc.).Após passar por essas etapas, o endpoint finish
obtém um objeto legível por humanos
contendo as claims do mdoc, por exemplo:
{ "family_name": "Doe", "given_name": "John", "birth_date": "1990-01-01" }
Este processo garante que o Verifier possa extrair de forma segura e confiável as informações necessárias da credencial mdoc para exibição e processamento posterior.
O endpoint finish
retorna um objeto JSON mínimo para o frontend:
{ "verified": true, "message": "Credencial mdoc verificada com sucesso!", "credentialData": { "family_name": "Doe", "given_name": "John", "birth_date": "1990-01-01" } }
O frontend recebe esta resposta em startVerification()
e simplesmente a persiste no
estado do React para que possamos renderizar um cartão de confirmação agradável ou exibir
claims individuais – por exemplo, “Bem-vindo, John Doe (nascido em 1990-01-01)!”.
Você agora tem um Verifier completo e funcional que usa as capacidades nativas de manipulação de credenciais do navegador. Veja como executá-lo localmente e o que você pode fazer para levá-lo de uma prova de conceito a uma aplicação pronta para produção.
Clone o Repositório:
git clone https://github.com/corbado/digital-credentials-example.git cd digital-credentials-example
Instale as Dependências:
npm install
Inicie o Banco de Dados: Certifique-se de que o Docker está em execução na sua máquina e, em seguida, inicie o contêiner MySQL:
docker-compose up -d
Execute a Aplicação:
npm run dev
Abra seu navegador em http://localhost:3000
, e você deverá ver a UI do Verifier.
Agora você pode usar sua CMWallet para escanear o QR code e completar o fluxo de
verificação.
Este tutorial fornece os blocos de construção fundamentais para um Verifier. Para torná-lo pronto para produção, você precisaria implementar vários recursos adicionais:
Validação Criptográfica Completa: A implementação atual usa uma verificação de
placeholder (mdocToken.length > 0
). Em um cenário real, você deve realizar a validação
criptográfica completa da assinatura mdoc contra a chave pública do
Issuer (por exemplo, resolvendo seu DID ou buscando seu certificado
de chave pública). Para padrões de resolução de DID, consulte a
especificação de Resolução de DID do W3C.
Verificação de Revogação do Issuer: As credenciais podem ser revogadas pelo emissor antes de sua data de expiração. Um Verifier de produção deve verificar o status da credencial consultando uma lista de revogação ou um endpoint de status fornecido pelo emissor. A Lista de Status de Credenciais Verificáveis do W3C fornece o padrão para listas de revogação de credenciais.
Tratamento de Erros e Segurança Robustos: Adicione tratamento de erros abrangente, validação de entrada, limitação de taxa (rate-limiting) nos endpoints da API e garanta que toda a comunicação seja via HTTPS (TLS) para proteger os dados em trânsito. As Diretrizes de Segurança de API da OWASP fornecem práticas recomendadas abrangentes de segurança de API.
Suporte para Múltiplos Tipos de Credenciais: Estenda a lógica para lidar com
diferentes valores de doctype
e formatos de credencial se você espera receber mais do
que apenas a credencial PID da
Identidade Digital
Europeia (EUDI). O
Modelo de Dados de Credenciais Verificáveis do W3C
fornece especificações abrangentes de formato de VC.
Este exemplo é intencionalmente focado no fluxo principal mediado pelo navegador para torná-lo fácil de entender. Os seguintes tópicos são considerados fora do escopo:
redirect_uri
ou
registro dinâmico de cliente.Ao construir sobre esta base e incorporar esses próximos passos, você pode desenvolver um Verifier robusto e seguro, capaz de confiar e validar credenciais digitais em suas próprias aplicações.
É isso! Com menos de 250 linhas de TypeScript, agora temos um Verifier de ponta a ponta que:
Em produção, você substituiria a validação de placeholder por verificações completas do ISO 18013-5, adicionaria consultas de revogação do emissor, limitação de taxa, logs de auditoria e, claro, TLS de ponta a ponta — mas os blocos de construção principais permanecem exatamente os mesmos.
Aqui estão alguns dos principais recursos, especificações e ferramentas usadas ou referenciadas neste tutorial:
Repositório do Projeto:
Especificações Chave:
Ferramentas:
Bibliotecas:
Related Articles
Table of Contents