Get your free and exclusive 80-page Banking Passkey Report
Back to Overview

Como construir um Verifier de Credenciais Digitais (Guia para Desenvolvedores)

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

Blog-Post-Header-Image

See the original blog version in English here.

DigitalCredentialsDemo Icon

Want to experience digital credentials in action?

Try Digital Credentials

1. Introdução#

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.

1.1 Como Funciona#

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:

  • Issuer (Emissor): Uma autoridade confiável (por exemplo, uma agência governamental, universidade ou banco) que assina criptograficamente e emite uma credencial para um usuário.
  • Holder (Titular): O usuário, que recebe a credencial e a armazena de forma segura em uma carteira digital pessoal em seu dispositivo.
  • Verifier (Verificador): Uma aplicação ou serviço que precisa verificar a credencial do usuário.

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.

1.2 Por Que os Verifiers São Essenciais (e Por Que Você Está Aqui)#

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.

2. Pré-requisitos para Construir um Verifier#

Antes de começar, certifique-se de que você tem:

  1. Compreensão Básica de Credenciais Digitais e mDoc
    • Este tutorial foca no formato ISO mDoc (por exemplo, para carteiras de motorista móveis) e não cobre outros formatos como W3C Verifiable Credentials (VCs). Familiaridade com os conceitos básicos do mdoc será útil.
  2. Docker e Docker Compose
    • Nosso projeto usa um banco de dados MySQL em um contêiner Docker para gerenciar o estado da sessão OIDC. Certifique-se de ter ambos instalados e em execução.
  3. Protocolo Escolhido: OpenID4VP
    • Usaremos o protocolo OpenID4VP (OpenID for Verifiable Presentations) para o fluxo de troca de credenciais.
  4. Tech Stack Preparado
    • Usaremos TypeScript (Node.js) para a lógica de backend.
    • Usaremos Next.js tanto para o backend (API routes) quanto para o frontend (UI).
    • Bibliotecas chave: bibliotecas de decodificação CBOR para análise de mdoc e um cliente MySQL.
  5. Credenciais de Teste e Wallet
  6. Conhecimento Básico de Criptografia
    • Entender assinaturas digitais e conceitos de chave pública/privada, pois se relacionam com mdoc e fluxos OIDC.

Agora vamos detalhar cada um desses pré-requisitos, começando pelos padrões e protocolos que sustentam este Verifier baseado em mdoc.

2.1 Escolhas de Protocolo#

Nosso Verifier foi construído para o seguinte:

Padrão / ProtocoloDescrição
W3C VCO Modelo de Dados de Credenciais Verificáveis do W3C. Ele define a estrutura padrão para credenciais digitais, incluindo claims, metadados e provas.
SD-JWTDivulgaçã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 mDocISO/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.
OpenID4VPOpenID 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:

  • OpenID4VP como o protocolo para solicitar e receber credenciais.
  • ISO mDoc como o formato da credencial (por exemplo, para carteiras de motorista móveis).

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.

2.1.1 ISO mDoc (Mobile Document)#

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.

2.1.2 OpenID4VP (OpenID for Verifiable Presentations)#

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:

  • Iniciar o fluxo de apresentação de credenciais (via QR code ou API do navegador)
  • Receber a credencial mdoc da wallet do usuário
  • Garantir uma troca de credenciais segura, com estado e que preserva a privacidade

2.2 Escolhas de Tech Stack#

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.

2.2.1 Linguagem: TypeScript#

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.

2.2.2 Framework: Next.js#

Next.js é nosso framework de escolha porque oferece uma experiência integrada e fluida para construir aplicações full-stack.

  • Para o Frontend: Usaremos Next.js com React para construir a interface do usuário onde o processo de verificação é iniciado (por exemplo, exibindo um QR code).
  • Para o Backend: Aproveitaremos as API Routes do Next.js para criar os endpoints do lado do servidor. Esses endpoints são responsáveis por criar solicitações OpenID4VP válidas и por atuar como redirect_uri para receber e verificar com segurança a resposta final da CMWallet.

2.2.3 Bibliotecas Chave#

Nossa implementação depende de um conjunto específico de bibliotecas para o frontend e o backend:

  • next: O framework Next.js, usado tanto para as rotas da API de backend quanto para a UI do frontend.
  • react e react-dom: Potencializam a interface do usuário do frontend.
  • cbor-web: Usado para decodificar credenciais mdoc codificadas em CBOR em objetos JavaScript utilizáveis.
  • mysql2: Fornece conectividade com o banco de dados MySQL para armazenar desafios e sessões de verificação.
  • uuid: Uma biblioteca para gerar strings de desafio únicas (nonces).
  • @types/uuid: Tipos TypeScript para a geração de UUID.

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.

2.3 Obtenha uma Wallet e Credenciais de Teste#

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):

  1. Baixe o arquivo APK usando o link acima diretamente no seu dispositivo Android.
  2. Abra as Configurações > Segurança do seu dispositivo.
  3. Habilite "Instalar aplicativos desconhecidos" para o navegador que você usou para baixar o arquivo.
  4. Localize o APK baixado na sua pasta "Downloads" e toque nele para iniciar a instalação.
  5. Siga as instruções na tela para concluir a instalação.
  6. Abra a CMWallet e você a encontrará pré-carregada com credenciais de teste, pronta para o fluxo de verificação.

Nota: Instale arquivos APK apenas de fontes confiáveis. O link fornecido é do repositório oficial do projeto.

2.4 Conhecimento em Criptografia#

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.

2.4.1 Assinaturas Digitais: A Base da Confiança#

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:

  • Autenticidade: Prova que a credencial foi de fato criada pelo Issuer e não por um impostor.
  • Integridade: Prova que a credencial não foi alterada ou adulterada desde que foi assinada.

2.4.2 Criptografia de Chave Pública/Privada#

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:

  1. O Issuer tem um par de chaves: uma chave privada, que é mantida em segredo, e uma chave pública, que é disponibilizada a todos (geralmente através de seu DID Document).
  2. Assinatura: Quando um Issuer cria uma credencial, ele usa sua chave privada para gerar uma assinatura digital única para os dados específicos daquela credencial.
  3. Verificação: Quando nosso Verifier recebe a credencial, ele usa a chave pública do Issuer para verificar a assinatura. Se a verificação for aprovada, o Verifier sabe que a credencial é autêntica e não foi adulterada. Qualquer alteração nos dados da credencial invalidaria a assinatura.

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.

2.4.3 Credenciais Verificáveis como JWTs#

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 (.):

  • Header: Contém metadados sobre o token, como o algoritmo de assinatura usado (alg).
  • Payload: Contém as declarações reais da Credencial Verificável (claim vc), incluindo o issuer, credentialSubject, etc.
  • Signature: A assinatura digital gerada pelo Issuer, que cobre o header e o payload.
// 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.

2.4.4 A Apresentação Verificável: Provando a Posse#

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:

  1. O Verifier pede ao usuário para apresentar uma credencial.
  2. A wallet do usuário cria uma Apresentação Verificável, agrupa a(s) credencial(is) necessária(s) dentro dela e assina a apresentação inteira usando a chave privada do Holder.
  3. A wallet envia esta VP assinada para o Verifier.

Nosso Verifier deve então realizar duas verificações de assinatura separadas:

  1. Verificar a(s) Credencial(is): Verificar a assinatura em cada VC dentro da apresentação usando a chave pública do Issuer. (Prova que a credencial é real).
  2. Verificar a Apresentação: Verificar a assinatura na própria VP usando a chave pública do Holder. (Prova que a pessoa que a apresenta é o proprietário).

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.

3. Visão Geral da Arquitetura#

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.

  • Frontend (Next.js & React): Um site leve voltado para o usuário. Seu trabalho é buscar um objeto de solicitação do nosso backend, passá-lo para a API navigator.credentials.get() do navegador, receber o resultado e encaminhá-lo ao nosso backend para verificação.
  • Backend (Next.js API Routes): O motor do Verifier. Ele gera um objeto de solicitação válido para a API do navegador e expõe um endpoint para receber a apresentação da credencial do frontend para validação final.
  • Navegador (Credential API): O facilitador. Ele recebe o objeto de solicitação do nosso frontend, entende o protocolo openid4vp e gera nativamente um QR code. Em seguida, aguarda a wallet retornar uma resposta.
  • CMWallet (App Móvel): A carteira do usuário. Ela escaneia o QR code, processa a solicitação, obtém o consentimento do usuário e envia a resposta assinada de volta para o navegador.

Aqui está um diagrama de sequência ilustrando o fluxo completo e preciso:

O Fluxo Explicado:

  1. Iniciação: O usuário clica no botão "Verificar" em nosso Frontend.
  2. Objeto de Solicitação: O frontend chama nosso Backend (/api/verify/start), que gera um objeto de solicitação contendo a query e um nonce, e então o retorna.
  3. Chamada da API do Navegador: O frontend chama navigator.credentials.get() com o objeto de solicitação.
  4. QR Code Nativo: O Navegador vê a solicitação do protocolo 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".

  1. Escanear e Apresentar: O usuário escaneia o QR code com a CMWallet. A wallet obtém a aprovação do usuário e envia a Apresentação Verificável de volta para o navegador.
  2. Resolução da Promise: O navegador recebe a resposta, e a promise .get() original no frontend finalmente é resolvida, entregando o payload da apresentação.
  3. Verificação no Backend: O frontend envia o payload da apresentação via POST para o endpoint /api/verify/finish do nosso backend. O backend valida o nonce e a credencial.
  4. Resultado: O backend retorna uma mensagem final de sucesso ou falha para o frontend, que atualiza a UI.

4. Construindo o Verifier#

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

4.1 Configurando o Projeto#

Primeiro, vamos inicializar um novo projeto Next.js, instalar as dependências necessárias e iniciar nosso banco de dados.

4.1.1 Inicializando a Aplicação Next.js#

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.

4.1.2 Instalando Dependências#

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.

4.1.3 Iniciando o Banco de Dados#

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.

4.2 Visão Geral da Arquitetura da Aplicação Next.js#

Nossa aplicação Next.js está estruturada para separar as responsabilidades entre o frontend e o backend, embora façam parte do mesmo projeto.

  • Frontend (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.
  • Rotas de API do Backend (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.
  • Biblioteca (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:

4.3 Construindo o Frontend#

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:

  • Interação do Usuário: Fornece uma interface simples, como um botão "Verificar", para o usuário iniciar o processo.
  • Gerenciamento de Estado: Gerencia o estado da UI, mostrando indicadores de carregamento enquanto a verificação está em andamento e exibindo a mensagem final de sucesso ou erro.
  • Comunicação com o Backend (Solicitação): Chama /api/verify/start e recebe um payload JSON estruturado (protocol, request, state) descrevendo exatamente o que a wallet deve apresentar.
  • Invocação da API do Navegador: Entrega esse objeto JSON para navigator.credentials.get(), que renderiza um QR code nativo e aguarda a resposta da wallet.
  • Comunicação com o Backend (Resposta): Assim que a API do navegador retorna a Apresentação Verificável, ele envia esses dados para nosso endpoint /api/verify/finish em uma solicitação POST para a validação final do lado do servidor.
  • Exibição de Resultados: Atualiza a UI para informar ao usuário se a verificação foi bem-sucedida ou falhou, com base na resposta do backend.

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.

Por que 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.

4.4 Construindo os Endpoints do Backend#

Com a UI do React no lugar, agora precisamos de duas rotas de API que fazem o trabalho pesado no servidor:

  1. /api/verify/start – constrói uma solicitação OpenID4VP, persiste um desafio de uso único no MySQL e entrega tudo de volta ao navegador.
  2. /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.

4.4.1 /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

noncedesafio 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.

4.4.2 Ajudantes do 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.


4.5 /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.

4.6 Decodificando a Credencial 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.

4.6.1 Etapa 1: Decodificação Base64URL e CBOR#

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; }
  • Base64URL para Base64: Converte a credencial de Base64URL para a codificação Base64 padrão.
  • Base64 para Binário: Decodifica a string Base64 em um array de bytes binário.
  • Decodificação CBOR: Usa a biblioteca cbor-web para decodificar os dados binários em um objeto JavaScript estruturado.

4.6.2 Etapa 2: Extraindo Claims com Namespace#

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; }
  • Itera sobre todos os documentos na credencial decodificada.
  • Decodifica cada namespace (por exemplo, eu.europa.ec.eudi.pid.1) para extrair os valores reais das claims (como nome, data de nascimento, etc.).
  • Lida com namespaces assinados pelo issuer e pelo dispositivo, se presentes.

Exemplo de Saída#

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.

4.7 Apresentando o Resultado na UI#

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)!”.

5. Executando o Verifier e Próximos Passos#

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.

5.1 Como Executar o Exemplo#

  1. Clone o Repositório:

    git clone https://github.com/corbado/digital-credentials-example.git cd digital-credentials-example
  2. Instale as Dependências:

    npm install
  3. 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
  4. 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.

5.2 Próximos Passos: Do Demo à Produçã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.

5.3 O Que Está Fora do Escopo Deste Tutorial#

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:

  • Segurança Pronta para Produção: O Verifier é para fins educacionais e carece do endurecimento necessário para um ambiente de produção.
  • Credenciais Verificáveis W3C: Este tutorial foca exclusivamente no formato ISO mDoc para carteiras de motorista móveis. Não cobre outros formatos populares como JWT-VCs ou VCs com Linked Data Proofs (LD-Proofs).
  • Fluxos Avançados do OpenID4VP: Não implementamos recursos mais complexos do OpenID4VP, como comunicação direta da wallet com o backend usando um 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.

Conclusão#

É isso! Com menos de 250 linhas de TypeScript, agora temos um Verifier de ponta a ponta que:

  1. Publica uma solicitação para a API de credenciais do navegador.
  2. Permite que qualquer wallet compatível forneça uma Apresentação Verificável.
  3. Valida a apresentação no servidor.
  4. Atualiza a UI em tempo real.

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.

Recursos#

Aqui estão alguns dos principais recursos, especificações e ferramentas usadas ou referenciadas neste tutorial:

Add passkeys to your app in <1 hour with our UI components, SDKs & guides.

Start Free Trial

Share this article


LinkedInTwitterFacebook