---
url: 'https://www.corbado.com/pt/blog/como-construir-emissor-de-credenciais-verificaveis'
title: 'Como construir um Emissor de Credenciais Digitais (Guia para Desenvolvedores)'
description: 'Aprenda a construir um emissor de Credenciais Verificáveis W3C usando o protocolo OpenID4VCI. Este guia passo a passo mostra como criar uma aplicação Next.js que emite credenciais assinadas criptograficamente e compatíveis com carteiras digitais.'
lang: 'pt'
author: 'Amine'
date: '2025-08-20T15:39:11.884Z'
lastModified: '2026-03-25T10:03:57.553Z'
keywords: 'emissor de credenciais digitais, tutorial emissor, construir emissor'
category: 'Digital Credentials'
---

# Como construir um Emissor de Credenciais Digitais (Guia para Desenvolvedores)

## 1. Introdução

As [Credenciais Digitais](https://www.corbado.com/pt/blog/credenciais-digitais-passkeys) são uma forma poderosa
de comprovar identidade e alegações de maneira segura e que preserva a privacidade. Mas
como os usuários obtêm essas credenciais? É aqui que o papel do **Emissor** se torna
crucial. Um Emissor é uma entidade confiável — como uma agência
[governamental](https://www.corbado.com/passkeys-for-public-sector), uma universidade ou um banco — responsável
por criar e distribuir credenciais assinadas digitalmente para os usuários.

Este guia oferece um [tutorial](https://www.corbado.com/pt/blog/aplicacao-crud-react-express-mysql) completo e
passo a passo para construir um Emissor de
[Credenciais Digitais](https://www.corbado.com/pt/blog/credenciais-digitais-passkeys). Vamos nos concentrar no
protocolo **OpenID for Verifiable Credential Issuance (OpenID4VCI)**, um padrão moderno
que define como os usuários podem obter credenciais de um Emissor e armazená-las com
segurança em suas wallets digitais.

O resultado final será uma aplicação [Next.js](https://www.corbado.com/blog/nextjs-passkeys) funcional que pode:

1. Aceitar dados do usuário por meio de um formulário web simples.
2. Gerar uma oferta de credencial segura e de uso único.
3. Exibir a oferta como um [código QR](https://www.corbado.com/pt/blog/login-codigo-qr-autenticacao) para o
   usuário escanear com sua [wallet](https://www.corbado.com/blog/digital-wallet-assurance) móvel.
4. Emitir uma credencial assinada criptograficamente que o usuário pode armazenar e
   apresentar para verificação.

### 1.1 Entendendo a Terminologia: Credenciais Digitais vs. Credenciais Verificáveis

Antes de prosseguirmos, é importante esclarecer a distinção entre dois conceitos
relacionados, mas diferentes:

- **Credenciais Digitais (Termo Geral):** Esta é uma categoria ampla que engloba qualquer
  forma digital de credenciais, certificados ou atestados. Isso pode incluir certificados
  digitais simples, selos digitais básicos ou qualquer credencial armazenada
  eletronicamente que pode ou não ter recursos de segurança criptográfica.

- **Credenciais Verificáveis (VCs - Padrão W3C):** Este é um tipo específico de credencial
  digital que segue o padrão W3C [Verifiable Credentials](https://www.corbado.com/glossary/microcredentials) Data
  Model. As Credenciais Verificáveis são credenciais assinadas criptograficamente, à prova
  de adulteração e que respeitam a privacidade, podendo ser verificadas de forma
  independente. Elas incluem requisitos técnicos específicos como:
    - Assinaturas criptográficas para autenticidade e integridade
    - Modelo de dados e formatos padronizados
    - Mecanismos de apresentação que preservam a privacidade
    - Protocolos de verificação interoperáveis

**Neste guia, estamos construindo especificamente um emissor de Credenciais Verificáveis**
que segue o padrão W3C, e não apenas um sistema de
[credenciais digitais](https://www.corbado.com/pt/blog/credenciais-digitais-passkeys) qualquer. O protocolo
[OpenID4VCI](https://www.corbado.com/glossary/openid4vci) que estamos usando foi projetado especificamente para a
emissão de Credenciais Verificáveis, e o formato JWT-VC que implementaremos é um formato
compatível com W3C para Credenciais Verificáveis.

### 1.2 Como Funciona

A mágica por trás das credenciais digitais reside em um modelo simples, mas poderoso, de
"**triângulo de confiança**", envolvendo três atores principais:

- **Emissor:** Uma autoridade confiável (ex: uma agência
  [governamental](https://www.corbado.com/passkeys-for-public-sector), universidade ou banco) que assina
  criptograficamente e emite uma credencial para um usuário. **Este é o papel que estamos
  construindo neste guia.**
- **Portador (Holder):** O usuário, que recebe a credencial e a armazena com segurança em
  uma [wallet](https://www.corbado.com/blog/digital-wallet-assurance) digital pessoal em seu dispositivo.
- **Verificador:** Uma aplicação ou serviço que precisa verificar a credencial do usuário.

![Ecossistema de Credenciais Verificáveis W3C](https://www.w3.org/TR/vc-data-model/diagrams/ecosystem.svg)

O fluxo de emissão é o primeiro passo neste ecossistema. O Emissor valida as informações
do usuário e fornece a ele uma credencial. Uma vez que o Portador tenha essa credencial em
sua [wallet](https://www.corbado.com/blog/digital-wallet-assurance), ele pode apresentá-la a um Verificador para
provar sua identidade ou alegações, completando o triângulo.

Aqui está uma rápida olhada na aplicação final em ação:

**Passo 1: Entrada de Dados do Usuário** O usuário preenche um formulário com suas
informações pessoais para solicitar uma nova credencial.
![Formulário de Entrada de Dados do Usuário](https://s3.eu-central-1.amazonaws.com/corbado-cloud-staging-website-assets/issuer_step_1_0733a9e1da.png)

**Passo 2: Geração da Oferta de Credencial** A aplicação gera uma oferta de credencial
segura, exibida como um [código QR](https://www.corbado.com/pt/blog/login-codigo-qr-autenticacao) e um código
pré-autorizado.
![Código QR da Oferta de Credencial](https://s3.eu-central-1.amazonaws.com/corbado-cloud-staging-website-assets/issuer_step_2_3f1881c473.png)

**Passo 3: Interação com a Wallet** O usuário escaneia o
[código QR](https://www.corbado.com/pt/blog/login-codigo-qr-autenticacao) com uma wallet compatível (ex: Sphereon
Wallet) e insere um PIN para autorizar a emissão.
![Oferta de Credencial na wallet](https://s3.eu-central-1.amazonaws.com/corbado-cloud-staging-website-assets/issuer_step_3_b80d689dfe.png)
![Inserção do Código PIN](https://s3.eu-central-1.amazonaws.com/corbado-cloud-staging-website-assets/issuer_step_4_ca8bad8d11.png)

**Passo 4: Credencial Emitida** A wallet recebe e armazena a nova credencial digital
emitida, pronta para uso futuro.
![Confirmando os detalhes da credencial](https://s3.eu-central-1.amazonaws.com/corbado-cloud-staging-website-assets/issuer_step_5_55b8150597.png)
![Credencial adicionada](https://s3.eu-central-1.amazonaws.com/corbado-cloud-staging-website-assets/issuer_step_6_7f5ac5745d.png)

## 2. Pré-requisitos para Construir um Emissor

Antes de mergulharmos no código, vamos cobrir o conhecimento fundamental e as ferramentas
que você precisará. Este guia assume que você tem uma familiaridade básica com conceitos
de desenvolvimento web, mas os seguintes pré-requisitos são essenciais para construir um
emissor de credenciais.

### 2.1 Escolhas de Protocolo

Nosso Emissor é construído sobre um conjunto de padrões abertos que garantem a
interoperabilidade entre wallets e serviços de emissão. Para este
[tutorial](https://www.corbado.com/pt/blog/aplicacao-crud-react-express-mysql), vamos focar nos seguintes:

| Padrão / Protocolo                                                | Descrição                                                                                                                                                                                                                                     |
| :---------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **OpenID4VCI**                                                    | **OpenID for Verifiable Credential Issuance.** Este é o protocolo central que usaremos. Ele define um fluxo padrão de como um usuário (através de sua wallet) pode solicitar e receber uma credencial de um Emissor.                          |
| **[JWT-VC](https://www.w3.org/TR/vc-data-model/#json-web-token)** | **Credenciais Verificáveis baseadas em JWT.** O formato da credencial que emitiremos. É um padrão W3C que codifica credenciais verificáveis como JSON Web Tokens (JWTs), tornando-as compactas e amigáveis para a web.                        |
| **[ISO mDoc](https://www.iso.org/standard/69084.html)**           | **ISO/IEC 18013-5.** O padrão internacional para Carteiras de Motorista móveis (mDLs). Embora emitamos um JWT-VC, as _claims_ dentro dele são estruturadas para serem compatíveis com o modelo de dados mDoc (ex: `eu.europa.ec.eudi.pid.1`). |
| **OAuth 2.0**                                                     | O framework de autorização subjacente usado pelo OpenID4VCI. Implementaremos um fluxo `pre-authorized_code`, que é um tipo de concessão específico projetado para emissão de credenciais segura e amigável ao usuário.                        |

#### 2.1.1 Fluxos de Autorização: Pré-Autorizado vs. Código de Autorização

O [OpenID4VCI](https://www.corbado.com/glossary/openid4vci) suporta dois fluxos de autorização primários para a
emissão de credenciais:

1. **Fluxo de Código Pré-Autorizado:** Neste fluxo, o Emissor gera um código de uso único
   e de curta duração (`pre-authorized_code`) que fica imediatamente disponível para o
   usuário. A wallet do usuário pode então trocar esse código diretamente por uma
   credencial. Este fluxo é ideal para cenários onde o usuário já está autenticado e
   presente no site do Emissor, pois proporciona uma experiência de emissão instantânea e
   contínua, sem redirecionamentos.

2. **Fluxo de Código de Autorização:** Este é o fluxo padrão do
   [OAuth 2.0](https://www.corbado.com/glossary/oauth2), onde o usuário é redirecionado para um servidor de
   autorização para dar consentimento. Após a aprovação, o servidor envia um
   `authorization_code` de volta para uma `redirect_uri` registrada. Este fluxo é mais
   adequado para aplicações de terceiros que iniciam o processo de emissão em nome do
   usuário.

**Para este tutorial, usaremos o fluxo `pre-authorized_code`.** Escolhemos essa abordagem
porque é mais simples и oferece uma experiência de usuário mais direta para o nosso caso
de uso específico: um usuário solicitando diretamente uma credencial do próprio site do
Emissor. Isso elimina a necessidade de redirecionamentos complexos e registro de cliente,
tornando a lógica central de emissão mais fácil de entender e implementar.

Essa combinação de padrões nos permite construir um emissor compatível com uma ampla gama
de wallets digitais e garante um processo seguro e padronizado para o usuário.

### 2.2 Escolhas da Stack de Tecnologia

Para construir nosso emissor, usaremos a mesma stack de tecnologia robusta e moderna que
usamos para o verificador, garantindo uma experiência de desenvolvimento consistente e de
alta qualidade.

#### 2.2.1 Linguagem: TypeScript

Usaremos **TypeScript** tanto para o nosso código de frontend quanto de backend. Sua
tipagem estática é inestimável em uma aplicação crítica de segurança como um emissor, pois
ajuda a prevenir erros comuns e melhora a qualidade geral e a manutenibilidade do código.

#### 2.2.2 Framework: Next.js

**Next.js** é nosso framework de escolha porque proporciona uma experiência integrada e
fluida para a construção de aplicações
[full-stack](https://www.corbado.com/pt/blog/aplicacao-crud-react-express-mysql).

- **Para o Frontend:** Usaremos [Next.js](https://www.corbado.com/blog/nextjs-passkeys) com
  [React](https://www.corbado.com/blog/react-passkeys) para construir a interface do usuário onde os usuários
  podem inserir seus dados para solicitar uma credencial.
- **Para o Backend:** Aproveitaremos as **API Routes do Next.js** para criar os endpoints
  do lado do servidor que lidam com o fluxo [OpenID4VCI](https://www.corbado.com/glossary/openid4vci), desde a
  geração de ofertas de credenciais até a emissão da credencial final assinada.

#### 2.2.3 Bibliotecas Principais

Nossa implementação dependerá de algumas bibliotecas principais para lidar com tarefas
específicas:

- **next**, **react** e **react-dom**: As bibliotecas centrais para nossa aplicação
  [Next.js](https://www.corbado.com/blog/nextjs-passkeys).
- **mysql2**: Um cliente [MySQL](https://www.corbado.com/blog/passkey-webauthn-database-guide) para
  [Node.js](https://www.corbado.com/blog/nodejs-passkeys), usado para armazenar códigos de autorização e dados de
  sessão.
- **uuid**: Uma biblioteca para gerar identificadores únicos, que usaremos para criar
  valores de `pre-authorized_code`.
- **jose**: Uma biblioteca robusta para lidar com JSON Web Signatures (JWS), que usaremos
  para assinar criptograficamente as credenciais que emitimos.

### 2.3 Obtenha uma Wallet de Teste

Para testar seu emissor, você precisará de uma wallet móvel que suporte o protocolo
OpenID4VCI. Para este [tutorial](https://www.corbado.com/pt/blog/aplicacao-crud-react-express-mysql),
recomendamos a **Sphereon Wallet**, que está disponível tanto para
[Android](https://www.corbado.com/blog/how-to-enable-passkeys-android) quanto para
[iOS](https://www.corbado.com/blog/how-to-enable-passkeys-ios).

**Como Instalar a Sphereon Wallet:**

1. **Baixe a wallet** da
   [Google Play Store](https://play.google.com/store/apps/details?id=com.sphereon.ssi.wallet)
   ou da [Apple App Store](https://apps.apple.com/us/app/sphereon-wallet/id1661096796).
2. Instale o aplicativo em seu dispositivo móvel.
3. Uma vez instalada, a wallet está pronta para receber ofertas de credenciais escaneando
   um código QR.

### 2.4 Conhecimento de Criptografia

A emissão de uma credencial é uma operação crítica de segurança que se baseia em conceitos
criptográficos fundamentais para garantir confiança e autenticidade.

#### 2.4.1 Assinaturas Digitais

No seu cerne, uma Credencial Verificável é um conjunto de alegações que foi **assinado
digitalmente** pelo Emissor. Essa assinatura oferece duas garantias:

- **Autenticidade:** Prova que a credencial foi criada por um Emissor legítimo.
- **Integridade:** Prova que a credencial não foi adulterada desde que foi emitida.

#### 2.4.2 Criptografia de Chave Pública/Privada

As assinaturas digitais são criadas usando
[criptografia de chave pública](https://www.corbado.com/pt/blog/webauthn-pubkeycredparams-credentialpublickey)/privada.
Veja como funciona:

1. **O Emissor tem um par de chaves:** uma **chave privada**, que é mantida em segredo e
   segura, e uma **chave pública** correspondente, que é disponibilizada publicamente.
2. **Assinatura:** Quando o Emissor cria uma credencial, ele usa sua **chave privada**
   para gerar uma assinatura digital única para os dados da credencial.
3. **Verificação:** Um Verificador pode, posteriormente, usar a **chave pública** do
   Emissor para verificar a assinatura. Se a verificação for bem-sucedida, o Verificador
   sabe que a credencial é autêntica e não foi alterada.

Em nossa implementação, geraremos um par de chaves de Curva Elíptica (EC) e usaremos o
algoritmo `ES256` para assinar o JWT-VC. A [chave pública](https://www.corbado.com/pt/glossary/jwks) é
incorporada no DID do Emissor (`did:web`), permitindo que qualquer Verificador a descubra
e valide a assinatura da credencial.\
**Nota:** A claim `aud` (audience) é omitida intencionalmente em nossos JWTs, pois a
credencial é projetada para ser de propósito geral e não vinculada a uma wallet
específica.\
Se você quiser restringir o uso a um público específico, inclua uma claim `aud` e a
configure adequadamente.

## 3. Visão Geral da Arquitetura

Nossa aplicação de Emissor é construída como um projeto Next.js
[full-stack](https://www.corbado.com/pt/blog/aplicacao-crud-react-express-mysql), com uma separação clara entre a
lógica do frontend e do backend. Essa arquitetura nos permite criar uma experiência de
usuário fluida, enquanto lidamos com todas as operações críticas de segurança no
servidor.\
**Importante:** As tabelas `verification_sessions` e `verified_credentials` incluídas no
SQL não são necessárias para este emissor, mas estão incluídas para fins de completude.

- **Frontend (`src/app/issue/page.tsx`):** Uma única página [React](https://www.corbado.com/blog/react-passkeys)
  que permite aos usuários inserir seus dados para solicitar uma credencial. Ela faz
  chamadas de API para o nosso backend para iniciar o processo de emissão.
- **Rotas de API do Backend (`src/app/api/issue/...`):** Um conjunto de endpoints do lado
  do servidor que implementam o protocolo OpenID4VCI.
    - `/.well-known/openid-credential-issuer`: Um endpoint de metadados público. Esta é a
      primeira URL que uma wallet verificará para descobrir as capacidades do emissor,
      incluindo seu servidor de autorização, endpoint de token, endpoint de credencial e
      os tipos de credenciais que oferece.
    - `/.well-known/openid-configuration`: Um endpoint de descoberta padrão do OpenID
      Connect. Embora intimamente relacionado ao anterior, este endpoint serve
      configurações mais amplas relacionadas ao OIDC e é frequentemente necessário для
      interoperabilidade com clientes OpenID padrão.
    - `/.well-known/did.json`: O Documento DID para nosso emissor. Ao usar o método
      `did:web`, este arquivo é usado para publicar as chaves públicas do emissor, que os
      verificadores podem usar para validar as assinaturas das credenciais que ele emite.
    - `authorize/route.ts`: Cria um `pre-authorized_code` e uma oferta de credencial.
    - `token/route.ts`: Troca o `pre-authorized_code` por um
      [access token](https://www.corbado.com/glossary/access-token).
    - `credential/route.ts`: Emite o JWT-VC final, assinado criptograficamente.
    - `schemas/pid/route.ts`: Expõe o esquema JSON para a credencial PID. Isso permite que
      qualquer consumidor da credencial entenda sua estrutura e tipos de dados.
- **Biblioteca (`src/lib/`):**
    - `database.ts`: Gerencia todas as interações com o banco de dados, como o
      armazenamento de códigos de autorização e chaves do emissor.
    - `crypto.ts`: Lida com todas as operações criptográficas, incluindo geração de chaves
      e assinatura de JWT.

Aqui está um diagrama ilustrando o fluxo de emissão:

![Fluxo de Emissão de Credencial Digital](https://s3.eu-central-1.amazonaws.com/corbado-cloud-staging-website-assets/Mermaid_Chart_Create_complex_visual_diagrams_with_text_A_smarter_way_of_creating_diagrams_2025_07_29_145228_d28fd13731.svg)

## 4. Construindo o Emissor

Agora que temos uma compreensão sólida dos padrões, protocolos e arquitetura, podemos
começar a construir nosso emissor.

> **Acompanhe ou Use o Código Final**
>
> Vamos agora passar pela configuração e implementação do código passo a passo. Se você
> preferir ir direto para o produto final, pode clonar o projeto completo do nosso
> repositório no GitHub e executá-lo localmente.
>
> ```bash
> 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 App Next.js

Abra seu terminal, navegue até o diretório onde você deseja criar seu projeto e execute o
seguinte comando. Estamos usando o App Router, TypeScript e Tailwind CSS para este
projeto.

```bash
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 no seu diretório atual.

#### 4.1.2 Instalando Dependências

Em seguida, precisamos instalar as bibliotecas que lidarão com JWTs, conexões de banco de
dados e geração de UUID.

```bash
npm install jose mysql2 uuid @types/uuid
```

Este comando instala:

- `jose`: Para assinar e verificar JSON Web Tokens (JWTs).
- `mysql2`: O cliente [MySQL](https://www.corbado.com/blog/passkey-webauthn-database-guide) 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](https://www.corbado.com/blog/passkey-webauthn-database-guide) para
armazenar códigos de autorização, sessões de emissão e chaves do emissor. 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:

```yaml
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 tanto para o verificador quanto para o
emissor:

```sql
-- 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)
);

-- ISSUER TABLES

-- Table for storing authorization codes in OpenID4VCI flow
CREATE TABLE IF NOT EXISTS authorization_codes (
    id VARCHAR(36) PRIMARY KEY,
    code VARCHAR(255) NOT NULL UNIQUE,
    client_id VARCHAR(255),
    scope VARCHAR(255),
    code_challenge VARCHAR(255),
    code_challenge_method VARCHAR(50),
    redirect_uri TEXT,
    user_pin VARCHAR(10),
    expires_at TIMESTAMP NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    used BOOLEAN DEFAULT FALSE,
    INDEX idx_code (code),
    INDEX idx_expires_at (expires_at)
);

-- Table for storing issuance sessions
CREATE TABLE IF NOT EXISTS issuance_sessions (
    id VARCHAR(36) PRIMARY KEY,
    authorization_code_id VARCHAR(36),
    access_token VARCHAR(255),
    token_type VARCHAR(50) DEFAULT 'Bearer',
    expires_in INT DEFAULT 3600,
    c_nonce VARCHAR(255),
    c_nonce_expires_at TIMESTAMP,
    status ENUM('pending', 'authorized', 'credential_issued', 'expired', 'failed') DEFAULT 'pending',
    user_data JSON,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    FOREIGN KEY (authorization_code_id) REFERENCES authorization_codes(id) ON DELETE CASCADE,
    INDEX idx_access_token (access_token),
    INDEX idx_c_nonce (c_nonce),
    INDEX idx_status (status)
);

-- Table for storing issued credentials
CREATE TABLE IF NOT EXISTS issued_credentials (
    id VARCHAR(36) PRIMARY KEY,
    session_id VARCHAR(36),
    credential_id VARCHAR(255),
    credential_type VARCHAR(255) DEFAULT 'jwt_vc',
    doctype VARCHAR(255) DEFAULT 'eu.europa.ec.eudi.pid.1',
    credential_data LONGTEXT, -- Base64 encoded mDoc
    credential_claims JSON,
    issuer_did VARCHAR(255),
    subject_id VARCHAR(255),
    issued_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    expires_at TIMESTAMP,
    revoked BOOLEAN DEFAULT FALSE,
    revoked_at TIMESTAMP NULL,
    FOREIGN KEY (session_id) REFERENCES issuance_sessions(id) ON DELETE CASCADE,
    INDEX idx_credential_id (credential_id),
    INDEX idx_session_id (session_id),
    INDEX idx_doctype (doctype),
    INDEX idx_subject_id (subject_id),
    INDEX idx_issued_at (issued_at)
);

-- Table for storing issuer keys (simplified for demo)
CREATE TABLE IF NOT EXISTS issuer_keys (
    id VARCHAR(36) PRIMARY KEY,
    key_id VARCHAR(255) NOT NULL UNIQUE,
    key_type VARCHAR(50) NOT NULL, -- 'EC', 'RSA'
    algorithm VARCHAR(50) NOT NULL, -- 'ES256', 'RS256', etc.
    public_key TEXT NOT NULL, -- JWK format
    private_key TEXT NOT NULL, -- JWK format (encrypted in production)
    is_active BOOLEAN DEFAULT TRUE,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    INDEX idx_key_id (key_id),
    INDEX idx_is_active (is_active)
);
```

Uma vez que ambos os arquivos estejam no lugar, abra seu terminal na raiz do projeto e
execute:

```bash
docker-compose up -d
```

Este comando iniciará um contêiner MySQL em segundo plano, pronto para nossa aplicação
usar.

### 4.2 Implementando as Bibliotecas Compartilhadas

Antes de construirmos os endpoints da API, vamos criar as bibliotecas compartilhadas que
lidarão com a lógica de negócio principal. Essa abordagem mantém nossas rotas de API
limpas e focadas em lidar com requisições HTTP, enquanto o trabalho complexo é delegado a
esses módulos.

#### 4.2.1 A Biblioteca de Banco de Dados (`src/lib/database.ts`)

Este arquivo é a única fonte da verdade para todas as interações com o banco de dados. Ele
usa a biblioteca `mysql2` para se conectar ao nosso contêiner MySQL e fornece um conjunto
de funções exportadas para criar, ler e atualizar registros em nossas tabelas. Essa camada
de abstração torna nosso código mais modular e fácil de manter.

Crie o arquivo `src/lib/database.ts` com o seguinte conteúdo:

```typescript
// src/lib/database.ts
import mysql from "mysql2/promise";

// Database connection configuration
const dbConfig = {
    host: process.env.DATABASE_HOST || "localhost",
    port: parseInt(process.env.DATABASE_PORT || "3306"),
    user: process.env.DATABASE_USER || "app_user",
    password: process.env.DATABASE_PASSWORD || "app_password",
    database: process.env.DATABASE_NAME || "digital_credentials",
    timezone: "+00:00",
};

let connection: mysql.Connection | null = null;

export async function getConnection(): Promise<mysql.Connection> {
    if (!connection) {
        connection = await mysql.createConnection(dbConfig);
    }
    return connection;
}

// Data-Access-Object (DAO) functions for each table
// ... (e.g., createChallenge, getChallenge, createAuthorizationCode, etc.)
```

> **Nota:** Por brevidade, a lista completa de funções DAO foi omitida. Você pode
> encontrar o código completo no
> [repositório do projeto](https://github.com/corbado/digital-credentials-example/blob/main/src/lib/database.ts).
> Este arquivo inclui funções para gerenciar desafios, sessões de verificação, códigos de
> autorização, sessões de emissão e chaves do emissor.

#### 4.2.2 A Biblioteca de Criptografia (`src/lib/crypto.ts`)

Este arquivo lida com todas as operações criptográficas críticas de segurança. Ele usa a
biblioteca `jose` para gerar pares de chaves e assinar JSON Web Tokens (JWTs).

**Geração de Chave** A função `generateIssuerKeyPair` cria um novo par de chaves de Curva
Elíptica que será usado para assinar credenciais. A [chave pública](https://www.corbado.com/pt/glossary/jwks) é
exportada no formato JSON Web Key (JWK) para que possa ser publicada em nosso documento
`did.json`.

```typescript
// src/lib/crypto.ts
import { generateKeyPair, exportJWK, SignJWT } from "jose";

export async function generateIssuerKeyPair(keyId: string, issuerDid: string) {
    const { publicKey, privateKey } = await generateKeyPair("ES256", {
        crv: "P-256",
        extractable: true,
    });

    const publicKeyJWK = await exportJWK(publicKey);
    publicKeyJWK.kid = keyId; // Assign a unique key ID

    // ... (private key export and other setup)

    return { publicKey, privateKey, publicKeyJWK /* ... */ };
}
```

**Criação de Credencial JWT** A função `createJWTVerifiableCredential` é o núcleo do
processo de emissão. Ela pega as alegações do usuário, o par de chaves do emissor e outros
metadados, e os usa para criar um JWT-VC assinado.

```typescript
// src/lib/crypto.ts

export async function createJWTVerifiableCredential(
    claims: MDocClaims,
    issuerKeyPair: IssuerKeyPair,
    subjectId: string,
    audience: string,
): Promise<string> {
    const now = Math.floor(Date.now() / 1000);
    const oneYear = 365 * 24 * 60 * 60;

    const vcPayload = {
        // The issuer's DID
        iss: issuerKeyPair.issuerDid,
        // The subject's (holder's) DID
        sub: subjectId,
        // The time the credential was issued (iat) and when it expires (exp)
        iat: now,
        exp: now + oneYear,
        // The Verifiable Credential data model
        vc: {
            "@context": [
                "https://www.w3.org/2018/credentials/v1",
                "https://europa.eu/eudi/pid/v1",
            ],
            type: ["VerifiableCredential", "eu.europa.ec.eudi.pid.1"],
            issuer: issuerKeyPair.issuerDid,
            issuanceDate: new Date(now * 1000).toISOString(),
            credentialSubject: {
                id: subjectId,
                ...claims,
            },
        },
    };

    // Sign the payload with the issuer's private key
    return await new SignJWT(vcPayload)
        .setProtectedHeader({
            alg: issuerKeyPair.algorithm,
            kid: issuerKeyPair.keyId,
            typ: "JWT",
        })
        .sign(issuerKeyPair.privateKey);
}
```

Esta função constrói o payload do JWT de acordo com o W3C
[Verifiable Credentials](https://www.corbado.com/glossary/microcredentials) Data Model e o assina com a chave
privada do emissor, produzindo uma credencial verificável segura.

### 4.2 Visão Geral da Arquitetura da App Next.js

Nossa aplicação Next.js é estruturada para separar as responsabilidades entre o frontend e
o backend, embora façam parte do mesmo projeto. Isso é alcançado aproveitando o App Router
tanto para páginas de UI quanto para endpoints de API.

- **Frontend (`src/app/issue/page.tsx`):** Um único componente de página
  [React](https://www.corbado.com/blog/react-passkeys) que define a UI para a rota `/issue`. Ele lida com a
  entrada do usuário e se comunica com nossa API de backend.

- **Rotas de API do Backend (`src/app/api/...`):**
    - **Descoberta (`.well-known/.../route.ts`):** Essas rotas expõem endpoints de
      metadados públicos que permitem que wallets e outros clientes descubram as
      capacidades e chaves públicas do emissor.
    - **Emissão (`issue/.../route.ts`):** Esses endpoints implementam a lógica principal
      do OpenID4VCI, incluindo a criação de ofertas de credenciais, emissão de tokens e
      assinatura da credencial final.
    - **Esquema (`schemas/pid/route.ts`):** Esta rota serve o esquema JSON para a
      credencial, definindo sua estrutura.

- **Biblioteca (`src/lib/`):** Este diretório contém lógica reutilizável compartilhada em
  todo o backend.
    - `database.ts`: Gerencia todas as interações com o banco de dados, abstraindo as
      consultas SQL.
    - `crypto.ts`: Lida com todas as operações criptográficas, como geração de chaves e
      assinatura de JWT.

Essa separação clara torna a aplicação modular e mais fácil de manter.

**Nota:** A função `generateIssuerDid()` deve retornar um `did:web` válido que corresponda
ao seu domínio de emissor.\
Quando implantado, o `.well-known/did.json` deve ser servido sobre HTTPS nesse domínio
para que os verificadores possam validar as credenciais.

![Visão geral da arquitetura da App Next.js](https://s3.eu-central-1.amazonaws.com/corbado-cloud-staging-website-assets/Mermaid_Chart_Create_complex_visual_diagrams_with_text_A_smarter_way_of_creating_diagrams_2025_07_29_151549_6a0aca6477.svg)

### 4.3 Construindo o Frontend

Nosso frontend é uma única página React que fornece um formulário simples para os usuários
solicitarem uma nova credencial digital. Suas responsabilidades são:

- Capturar dados do usuário (nome, data de nascimento, etc.).
- Enviar esses dados ao nosso backend para criar uma oferta de credencial.
- Exibir o código QR e o PIN resultantes para o usuário escanear com sua wallet.

A lógica principal é tratada na função `handleSubmit`, que é acionada quando o usuário
envia o formulário.

```typescript
// src/app/issue/page.tsx

const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    setLoading(true);
    setError(null);
    setCredentialOffer(null);

    try {
        // 1. Validate required fields
        if (!userData.given_name || !userData.family_name || !userData.birth_date) {
            throw new Error("Please fill in all required fields");
        }

        // 2. Request a credential offer from the backend
        const response = await fetch("/api/issue/authorize", {
            method: "POST",
            headers: {
                "Content-Type": "application/json",
            },
            body: JSON.stringify({
                user_data: userData,
            }),
        });

        if (!response.ok) {
            const errorData = await response.json();
            throw new Error(
                errorData.error_description || "Failed to create credential offer",
            );
        }

        // 3. Set the credential offer in state to display the QR code
        const result = await response.json();
        setCredentialOffer(result);
    } catch (err) {
        const errorMessage = (err as Error).message || "Unknown error occurred";
        setError(errorMessage);
    } finally {
        setLoading(false);
    }
};
```

Esta função executa três ações principais:

1. **Valida os dados do formulário** para garantir que todos os campos obrigatórios sejam
   preenchidos.
2. **Envia uma requisição `POST`** para nosso endpoint `/api/issue/authorize` com os dados
   do usuário.
3. **Atualiza o estado do componente** com a oferta de credencial recebida do backend, o
   que aciona a UI para exibir o código QR e o código de transação.

O resto do arquivo contém código React padrão para renderizar o formulário e a exibição do
código QR. Você pode ver o arquivo completo no
[repositório do projeto](https://github.com/corbado/digital-credentials-example/blob/main/src/app/issue/page.tsx).

### 4.4 Configurando o Ambiente e a Descoberta

Antes de construirmos a API do backend, precisamos configurar nosso ambiente e os
endpoints de descoberta. Esses arquivos `.well-known` são cruciais para que as wallets
encontrem nosso emissor e entendam como interagir com ele.

#### 4.4.1 Crie o Arquivo de Ambiente

Crie um arquivo chamado `.env.local` na raiz do seu projeto e adicione a seguinte linha.
Esta URL deve ser publicamente acessível para que uma wallet móvel possa alcançá-la. Para
desenvolvimento local, você pode usar um serviço de tunelamento como o
[ngrok](https://www.corbado.com/blog/multi-device-passkey-login-corbado-ngrok) para expor seu `localhost`.

```
NEXT_PUBLIC_BASE_URL=http://localhost:3000
```

#### 4.4.2 Implemente os Endpoints de Descoberta

As wallets descobrem as capacidades de um emissor consultando URLs `.well-known` padrão.
Precisamos criar três desses endpoints.

**1. Metadados do Emissor (`/.well-known/openid-credential-issuer`)**

Este é o arquivo de descoberta principal para o OpenID4VCI. Ele informa à wallet tudo o
que ela precisa saber sobre o emissor, incluindo seus endpoints, os tipos de credenciais
que oferece e os algoritmos criptográficos suportados.

Crie o arquivo `src/app/.well-known/openid-credential-issuer/route.ts`:

```typescript
// src/app/.well-known/openid-credential-issuer/route.ts
import { NextResponse } from "next/server";

export async function GET() {
    const baseUrl = process.env.NEXT_PUBLIC_BASE_URL || "http://localhost:3000";

    const issuerMetadata = {
        // O identificador único do emissor.
        issuer: baseUrl,
        // A URL do servidor de autorização. Para simplificar, nosso emissor é seu próprio servidor de autorização.
        authorization_servers: [baseUrl],
        // A URL do emissor da credencial.
        credential_issuer: baseUrl,
        // O endpoint onde a wallet fará POST para receber a credencial real.
        credential_endpoint: `${baseUrl}/api/issue/credential`,
        // O endpoint onde a wallet troca um código de autorização por um token de acesso.
        token_endpoint: `${baseUrl}/api/issue/token`,
        // O endpoint para o fluxo de autorização (não usado em nosso fluxo pré-autorizado, mas é uma boa prática incluir).
        authorization_endpoint: `${baseUrl}/api/issue/authorize`,
        // Indica suporte ao fluxo de código pré-autorizado sem exigir autenticação do cliente.
        pre_authorized_grant_anonymous_access_supported: true,
        // Informações legíveis por humanos sobre o emissor.
        display: [
            {
                name: "Corbado Credentials Issuer",
                locale: "en-US",
            },
        ],
        // Uma lista dos tipos de credenciais que este emissor pode emitir.
        credential_configurations_supported: {
            "eu.europa.ec.eudi.pid.1": {
                // O formato da credencial (ex: jwt_vc, mso_mdoc).
                format: "jwt_vc",
                // O tipo de documento específico, em conformidade com os padrões ISO mDoc.
                doctype: "eu.europa.ec.eudi.pid.1",
                // O escopo OAuth 2.0 associado a este tipo de credencial.
                scope: "eu.europa.ec.eudi.pid.1",
                // Métodos que a wallet pode usar para provar a posse de sua chave.
                cryptographic_binding_methods_supported: ["jwk"],
                // Algoritmos de assinatura que o emissor suporta para esta credencial.
                credential_signing_alg_values_supported: ["ES256"],
                // Tipos de prova de posse que a wallet pode usar.
                proof_types_supported: {
                    jwt: {
                        proof_signing_alg_values_supported: ["ES256", "ES384", "ES512"],
                    },
                },
                // Propriedades de exibição para a credencial.
                display: [
                    {
                        name: "Corbado Credential Issuer",
                        locale: "en-US",
                        logo: {
                            uri: `${baseUrl}/logo.png`,
                            alt_text: "EU Digital Identity",
                        },
                        background_color: "#003399",
                        text_color: "#FFFFFF",
                    },
                ],
                // Uma lista das alegações (atributos) na credencial.
                claims: {
                    "eu.europa.ec.eudi.pid.1": {
                        given_name: {
                            mandatory: true,
                            display: [{ name: "Given Name", locale: "en-US" }],
                        },
                        family_name: {
                            mandatory: true,
                            display: [{ name: "Family Name", locale: "en-US" }],
                        },
                        birth_date: {
                            mandatory: true,
                            display: [{ name: "Date of Birth", locale: "en-US" }],
                        },
                    },
                },
            },
        },
        // Métodos de autenticação suportados pelo endpoint de token. 'none' significa cliente público.
        token_endpoint_auth_methods_supported: ["none"],
        // Métodos de desafio de código PKCE suportados.
        code_challenge_methods_supported: ["S256"],
        // Tipos de concessão OAuth 2.0 que o emissor suporta.
        grant_types_supported: [
            "authorization_code",
            "urn:ietf:params:oauth:grant-type:pre-authorized_code",
        ],
    };

    return NextResponse.json(issuerMetadata, {
        headers: {
            "Content-Type": "application/json",
            "Cache-Control": "no-cache, no-store, must-revalidate",
            Pragma: "no-cache",
            Expires: "0",
        },
    });
}
```

**2. Configuração OpenID (`/.well-known/openid-configuration`)**

Este é um documento de descoberta OIDC padrão que fornece um conjunto mais amplo de
detalhes de configuração.

Crie o arquivo `src/app/.well-known/openid-configuration/route.ts`:

```typescript
// src/app/.well-known/openid-configuration/route.ts
import { NextResponse } from "next/server";

export async function GET() {
    const baseUrl = process.env.NEXT_PUBLIC_BASE_URL || "http://localhost:3000";

    const openidConfiguration = {
        // O identificador único do emissor.
        credential_issuer: baseUrl,
        // O endpoint onde a wallet fará POST para receber a credencial real.
        credential_endpoint: `${baseUrl}/api/issue/credential`,
        // O endpoint para o fluxo de autorização.
        authorization_endpoint: `${baseUrl}/api/issue/authorize`,
        // O endpoint onde a wallet troca um código de autorização por um token de acesso.
        token_endpoint: `${baseUrl}/api/issue/token`,
        // Uma lista dos tipos de credenciais que este emissor pode emitir.
        credential_configurations_supported: {
            "eu.europa.ec.eudi.pid.1": {
                format: "jwt_vc",
                scope: "eu.europa.ec.eudi.pid.1",
                cryptographic_binding_methods_supported: ["jwk"],
                credential_signing_alg_values_supported: ["ES256", "ES384", "ES512"],
                proof_types_supported: {
                    jwt: {
                        proof_signing_alg_values_supported: ["ES256", "ES384", "ES512"],
                    },
                },
            },
        },
        // Tipos de concessão OAuth 2.0 que o emissor suporta.
        grant_types_supported: [
            "authorization_code",
            "urn:ietf:params:oauth:grant-type:pre-authorized_code",
        ],
        // Indica suporte ao fluxo de código pré-autorizado.
        pre_authorized_grant_anonymous_access_supported: true,
        // Métodos de desafio de código PKCE suportados.
        code_challenge_methods_supported: ["S256"],
        // Métodos de autenticação suportados pelo endpoint de token.
        token_endpoint_auth_methods_supported: ["none"],
        // Escopos OAuth 2.0 que o emissor suporta.
        scopes_supported: ["eu.europa.ec.eudi.pid.1"],
    };

    return NextResponse.json(openidConfiguration, {
        headers: {
            "Content-Type": "application/json",
            "Cache-Control": "no-cache, no-store, must-revalidate",
            Pragma: "no-cache",
            Expires: "0",
        },
    });
}
```

**3. Documento DID (`/.well-known/did.json`)**

Este arquivo publica a [chave pública](https://www.corbado.com/pt/glossary/jwks) do emissor usando o método
`did:web`, permitindo que qualquer pessoa verifique a assinatura das credenciais emitidas
por ele.

Crie o arquivo `src/app/.well-known/did.json/route.ts`:

```typescript
// src/app/.well-known/did.json/route.ts
import { NextResponse } from "next/server";
import { getActiveIssuerKey } from "../../../lib/database";
import { generateIssuerDid } from "../../../lib/crypto";

export async function GET() {
    const baseUrl = process.env.NEXT_PUBLIC_BASE_URL || "http://localhost:3000";
    const issuerKey = await getActiveIssuerKey();

    if (!issuerKey) {
        return NextResponse.json(
            { error: "No active issuer key found" },
            { status: 404 },
        );
    }

    const publicKeyJWK = JSON.parse(issuerKey.public_key);
    const didId = generateIssuerDid();
    const didDocument = {
        // O contexto define o vocabulário usado no documento.
        "@context": [
            "https://www.w3.org/ns/did/v1",
            "https://w3id.org/security/suites/jws-2020/v1",
        ],
        // A URI do DID, que é o identificador único para o emissor.
        id: didId,
        // O controlador do DID, que é a entidade que controla o DID. Aqui, é o próprio emissor.
        controller: didId,
        // Uma lista de chaves públicas que podem ser usadas para verificar assinaturas do emissor.
        verificationMethod: [
            {
                // Um identificador único para a chave, no escopo do DID.
                id: `${didId}#${issuerKey.key_id}`,
                // O tipo da chave.
                type: "JsonWebKey2020",
                // O DID do controlador da chave.
                controller: didId,
                // A chave pública no formato JWK.
                publicKeyJwk: publicKeyJWK,
            },
        ],
        // Especifica quais chaves podem ser usadas para autenticação (provando o controle do DID).
        authentication: [`${didId}#${issuerKey.key_id}`],
        // Especifica quais chaves podem ser usadas para criar credenciais verificáveis.
        assertionMethod: [`${didId}#${issuerKey.key_id}`],
        // Uma lista de serviços fornecidos pelo sujeito do DID, como o endpoint do emissor.
        service: [
            {
                id: `${didId}#openid-credential-issuer`,
                type: "OpenIDCredentialIssuer",
                serviceEndpoint: `${baseUrl}/.well-known/openid-credential-issuer`,
            },
        ],
    };

    return NextResponse.json(didDocument, {
        headers: {
            "Content-Type": "application/did+json",
            "Cache-Control": "no-cache, no-store, must-revalidate",
            Pragma: "no-cache",
            Expires: "0",
        },
    });
}
```

> **Por que sem Cache?** Você notará que todos os três endpoints retornam cabeçalhos que
> impedem agressivamente o cache (`Cache-Control: no-cache`, `Pragma: no-cache`,
> `Expires: 0`). Esta é uma prática de segurança crítica para documentos de descoberta. As
> configurações do emissor podem mudar — por exemplo, uma chave criptográfica pode ser
> rotacionada. Se uma wallet ou cliente armazenasse em cache uma versão antiga do arquivo
> `did.json` ou `openid-credential-issuer`, não conseguiria validar novas credenciais ou
> interagir com endpoints atualizados. Ao forçar os clientes a buscar uma cópia nova em
> cada requisição, garantimos que eles sempre tenham as informações mais atualizadas.

#### 4.4.3 Implemente o Endpoint do Esquema da Credencial

A peça final da nossa [infraestrutura](https://www.corbado.com/passkeys-for-critical-infrastructure) voltada para
o público é o endpoint do esquema da credencial. Esta rota serve um Esquema JSON que
define formalmente a estrutura, os tipos de dados e as restrições da credencial PID que
estamos emitindo. Wallets e verificadores podem usar este esquema para validar o conteúdo
da credencial.

Crie o arquivo `src/app/api/schemas/pid/route.ts` com o seguinte conteúdo:

```typescript
// src/app/api/schemas/pid/route.ts
import { NextResponse } from "next/server";

export async function GET() {
    const schema = {
        $schema: "https://json-schema.org/draft/2020-12/schema",
        $id: "https://example.com/schemas/pid", // Replace with your actual domain
        title: "PID Credential",
        description:
            "A schema for a Verifiable Credential representing a Personal Identification Document (PID).",
        type: "object",
        properties: {
            credentialSubject: {
                type: "object",
                properties: {
                    given_name: { type: "string" },
                    family_name: { type: "string" },
                    birth_date: { type: "string", format: "date" },
                    // ... other properties of the credential subject
                },
                required: ["given_name", "family_name", "birth_date"],
            },
            // ... other top-level properties of a Verifiable Credential
        },
    };

    return NextResponse.json(schema, {
        headers: {
            "Content-Type": "application/schema+json",
            "Access-Control-Allow-Origin": "*", // Allow cross-origin requests
        },
    });
}
```

> **Nota:** O Esquema JSON para uma credencial PID pode ser bastante grande e detalhado.
> Por brevidade, o esquema completo foi truncado. Você pode encontrar o arquivo completo
> no
> [repositório do projeto](https://github.com/corbado/digital-credentials-example/blob/main/src/app/api/schemas/pid/route.ts).

### 4.5 Construindo os Endpoints do Backend

Com o frontend pronto, agora precisamos da lógica do lado do servidor para lidar com o
fluxo OpenID4VCI. Começaremos com o primeiro endpoint que o frontend chama:
`/api/issue/authorize`.

#### 4.5.1 `/api/issue/authorize`: Crie a Oferta de Credencial

Este endpoint é responsável por pegar os dados do usuário, gerar um código seguro de uso
único e construir uma `credential_offer` que a wallet do usuário possa entender.

Aqui está a lógica principal:

```typescript
// src/app/api/issue/authorize/route.ts
import { NextRequest, NextResponse } from "next/server";
import { v4 as uuidv4 } from "uuid";
import { createAuthorizationCode } from "@/lib/database";

export async function POST(request: NextRequest) {
    try {
        const body = await request.json();
        const { user_data } = body;

        // 1. Validate user data
        if (
            !user_data ||
            !user_data.given_name ||
            !user_data.family_name ||
            !user_data.birth_date
        ) {
            return NextResponse.json({ error: "missing_user_data" }, { status: 400 });
        }

        // 2. Generate a pre-authorized code and a PIN
        const code = uuidv4();
        const expiresAt = new Date(Date.now() + 10 * 60 * 1000); // 10 minutes
        const txCode = Math.floor(1000 + Math.random() * 9000).toString(); // 4-digit PIN

        // 3. Store the code and user data
        await createAuthorizationCode(uuidv4(), code, expiresAt);
        // Note: This uses an in-memory store for demo purposes only.
        // In production, persist data securely in a database with proper expiry.
        if (!(global as any).userDataStore) (global as any).userDataStore = new Map();
        (global as any).userDataStore.set(code, user_data);
        if (!(global as any).txCodeStore) (global as any).txCodeStore = new Map();
        (global as any).txCodeStore.set(code, txCode);

        // 4. Create the credential offer object
        const baseUrl = process.env.NEXT_PUBLIC_BASE_URL || "http://localhost:3000";
        const credentialOffer = {
            // The issuer's identifier, which is its base URL.
            credential_issuer: baseUrl,
            // An array of credential types the issuer is offering.
            credential_configuration_ids: ["eu.europa.ec.eudi.pid.1"],
            // Specifies the grant types the wallet can use.
            grants: {
                // We are using the pre-authorized code flow.
                "urn:ietf:params:oauth:grant-type:pre-authorized_code": {
                    // The one-time code the wallet will exchange for a token.
                    "pre-authorized_code": code,
                    // Indicates that the user must enter a PIN (tx_code) to redeem the code.
                    user_pin_required: true,
                },
            },
        };

        // 5. Create the full credential offer URI (a deep link for wallets)
        const credentialOfferUri = `openid-credential-offer://?credential_offer=${encodeURIComponent(
            JSON.stringify(credentialOffer),
        )}`;

        // The final response to the frontend.
        return NextResponse.json({
            // The deep link for the QR code.
            credential_offer_uri: credentialOfferUri,
            // The raw pre-authorized code, for display or manual entry.
            pre_authorized_code: code,
            // The 4-digit PIN the user must enter in their wallet.
            tx_code: txCode,
        });
    } catch (error) {
        console.error("Authorization error:", error);
        return NextResponse.json({ error: "server_error" }, { status: 500 });
    }
}
```

Passos chave neste endpoint:

1. **Validar Dados:** Primeiro, ele garante que os dados do usuário necessários estejam
   presentes.
2. **Gerar Códigos:** Ele cria um `pre-authorized_code` único (um UUID) e um `tx_code` de
   4 dígitos (PIN) para uma camada extra de segurança.
3. **Persistir Dados:** O `pre-authorized_code` é armazenado no banco de dados com um
   tempo de expiração curto. Os dados do usuário e o PIN são armazenados na memória,
   vinculados ao código.
4. **Construir Oferta:** Ele constrói o objeto `credential_offer` de acordo com a
   especificação OpenID4VCI. Este objeto informa à wallet onde está o emissor, quais
   credenciais ele oferece e o código necessário para obtê-las.
5. **Retornar URI:** Finalmente, ele cria uma URI de link profundo
   (`openid-credential-offer://...`) e a retorna para o frontend, juntamente com o
   `tx_code` para o usuário ver.

#### 4.5.2 `/api/issue/token`: Troque o Código por um Token

Assim que o usuário escaneia o código QR e insere seu PIN, a wallet faz uma requisição
`POST` para este endpoint. Seu trabalho é validar o `pre-authorized_code` e o `user_pin`
(PIN) e, se forem válidos, emitir um [access token](https://www.corbado.com/glossary/access-token) de curta
duração.

Crie o arquivo `src/app/api/issue/token/route.ts` com o seguinte conteúdo:

```typescript
// src/app/api/issue/token/route.ts
import { NextRequest, NextResponse } from "next/server";
import { v4 as uuidv4 } from "uuid";
import {
    getAuthorizationCode,
    markAuthorizationCodeAsUsed,
    createIssuanceSession,
} from "@/lib/database";

export async function POST(request: NextRequest) {
    try {
        const formData = await request.formData();
        const grant_type = formData.get("grant_type") as string;
        const code = formData.get("pre-authorized_code") as string;
        const user_pin = formData.get("user_pin") as string;

        // 1. Validate the grant type
        if (grant_type !== "urn:ietf:params:oauth:grant-type:pre-authorized_code") {
            return NextResponse.json(
                { error: "unsupported_grant_type" },
                { status: 400 },
            );
        }

        // 2. Validate the pre-authorized code
        const authCode = await getAuthorizationCode(code);
        if (!authCode) {
            return NextResponse.json(
                {
                    error: "invalid_grant",
                    error_description: "Invalid or expired code",
                },
                { status: 400 },
            );
        }

        // 3. Validate the PIN (tx_code)
        const expectedTxCode = (global as any).txCodeStore?.get(code);
        if (expectedTxCode !== user_pin) {
            return NextResponse.json(
                { error: "invalid_grant", error_description: "Invalid PIN" },
                { status: 400 },
            );
        }

        // 4. Generate access token and c_nonce
        const accessToken = uuidv4();
        const cNonce = uuidv4();
        const cNonceExpiresAt = new Date(Date.now() + 5 * 60 * 1000); // 5 minutes

        // 5. Create a new issuance session
        const userData = (global as any).userDataStore?.get(code);
        await createIssuanceSession(
            uuidv4(),
            authCode.id,
            accessToken,
            cNonce,
            cNonceExpiresAt,
            userData,
        );

        // 6. Mark the code as used and clean up temporary data
        await markAuthorizationCodeAsUsed(code);
        (global as any).txCodeStore?.delete(code);
        (global as any).userDataStore?.delete(code);

        // 7. Return the access token response
        return NextResponse.json({
            access_token: accessToken,
            token_type: "Bearer",
            expires_in: 3600, // 1 hour
            c_nonce: cNonce,
            c_nonce_expires_in: 300, // 5 minutes
        });
    } catch (error) {
        console.error("Token endpoint error:", error);
        return NextResponse.json({ error: "server_error" }, { status: 500 });
    }
}
```

Passos chave neste endpoint:

1. **Validar Tipo de Concessão:** Ele garante que a wallet esteja usando o tipo de
   concessão `pre-authorized_code` correto.
2. **Validar Código:** Ele verifica se o `pre-authorized_code` existe no banco de dados,
   não está expirado e não foi usado antes.
3. **Validar PIN:** Ele compara o `user_pin` da wallet com o `tx_code` que armazenamos
   anteriormente para garantir que o usuário autorizou a transação.
4. **Gerar Tokens:** Ele cria um `access_token` seguro e um `c_nonce` (nonce de
   credencial), que é um valor de uso único para prevenir ataques de repetição no endpoint
   de credencial.
5. **Criar Sessão:** Ele cria um novo registro `issuance_sessions` no banco de dados,
   vinculando o [access token](https://www.corbado.com/glossary/access-token) aos dados do usuário.
6. **Marcar Código como Usado:** Para evitar que a mesma oferta seja usada duas vezes, ele
   marca o `pre-authorized_code` como usado.
7. **Retornar Token:** Ele retorna o `access_token` e o `c_nonce` para a wallet.

#### 4.5.3 `/api/issue/credential`: Emita a Credencial Assinada

Este é o endpoint final e mais importante. A wallet usa o access token que recebeu do
endpoint `/token` para fazer uma requisição `POST` autenticada a esta rota. O trabalho
deste endpoint é realizar a validação final, criar a credencial assinada
criptograficamente e retorná-la para a wallet.

Crie o arquivo `src/app/api/issue/credential/route.ts` com o seguinte conteúdo:

```typescript
// src/app/api/issue/credential/route.ts
import { NextRequest, NextResponse } from "next/server";
import { v4 as uuidv4 } from "uuid";
import {
    getIssuanceSessionByToken,
    updateIssuanceSession,
    createIssuedCredential,
    getActiveIssuerKey,
} from "@/lib/database";
import {
    createJWTVerifiableCredential,
    importIssuerKeyPair,
    generateIssuerDid,
} from "@/lib/crypto";

export async function POST(request: NextRequest) {
    try {
        // 1. Validate the Bearer token
        const authHeader = request.headers.get("authorization");
        const accessToken = authHeader?.substring(7);
        const session = await getIssuanceSessionByToken(accessToken);

        if (!session) {
            return NextResponse.json({ error: "invalid_token" }, { status: 401 });
        }

        // 2. Get the user data from the session
        const userData = session.user_data;
        if (!userData) {
            return NextResponse.json({ error: "missing_user_data" }, { status: 400 });
        }

        // 3. Get the active issuer key
        const issuerKey = await getActiveIssuerKey();
        if (!issuerKey) {
            // In a real application, you would have a more robust key management system.
            // For this demo, we can generate a key on the fly if one doesn't exist.
            // This part is omitted for brevity but is in the repository.
            return NextResponse.json(
                {
                    error: "server_error",
                    error_description: "Failed to get issuer key",
                },
                { status: 500 },
            );
        }

        // 4. Create the JWT-VC
        const issuerDid = generateIssuerDid();
        const keyPair = await importIssuerKeyPair(
            issuerKey.key_id,
            issuerKey.public_key,
            issuerKey.private_key,
            issuerDid,
        );
        const subjectId = `did:example:${uuidv4()}`;
        const credentialData = await createJWTVerifiableCredential(
            userData,
            keyPair,
            subjectId,
            process.env.NEXT_PUBLIC_BASE_URL || "http://localhost:3000",
        );

        // 5. Store the issued credential in the database
        await createIssuedCredential(/* ... credential details ... */);
        await updateIssuanceSession(session.id, "credential_issued");

        // 6. Return the signed credential
        return NextResponse.json({
            format: "jwt_vc",
            credential: credentialData,
            c_nonce: uuidv4(), // A new nonce for subsequent requests
            c_nonce_expires_in: 300,
        });
    } catch (error) {
        console.error("Credential endpoint error:", error);
        return NextResponse.json({ error: "server_error" }, { status: 500 });
    }
}
```

Passos chave neste endpoint:

1. **Validar Token:** Ele verifica a existência de um token `Bearer` válido no cabeçalho
   `Authorization` e o usa para procurar a sessão de emissão ativa.
2. **Recuperar Dados do Usuário:** Ele recupera os dados de alegações do usuário, que
   foram armazenados na sessão quando o token foi criado.
3. **Carregar Chave do Emissor:** Ele carrega a chave de assinatura ativa do emissor do
   banco de dados. Em um cenário real, isso seria gerenciado por um sistema de
   gerenciamento de chaves seguro.
4. **Criar Credencial:** Ele chama nossa função auxiliar `createJWTVerifiableCredential`
   de `src/lib/crypto.ts` para construir e assinar o JWT-VC.
5. **Registrar Emissão:** Ele salva um registro da credencial emitida no banco de dados
   para fins de auditoria e revogação.
6. **Retornar Credencial:** Ele retorna a credencial assinada para a wallet em uma
   resposta JSON. A wallet é então responsável por armazená-la com segurança.

## 5. Executando o Emissor e Próximos Passos

Agora você tem uma implementação completa, de ponta a ponta, de um emissor de credenciais
digitais. Veja como executá-lo localmente e o que você precisa considerar 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:**

    ```bash
    git clone https://github.com/corbado/digital-credentials-example.git
    cd digital-credentials-example
    ```

2. **Instale as Dependências:**

    ```bash
    npm install
    ```

3. **Inicie o Banco de Dados:** Certifique-se de que o Docker esteja em execução e, em
   seguida, inicie o contêiner MySQL:

    ```bash
    docker-compose up -d
    ```

4. **Configure o Ambiente e Execute o Túnel:** Este é o passo mais crítico para o teste
   local. Como sua wallet móvel precisa se conectar à sua máquina de desenvolvimento pela
   internet, você deve expor seu servidor local com uma URL HTTPS pública. Usaremos o
   `ngrok` para isso.

    a. **Inicie o ngrok:**

    ```bash
    ngrok http 3000
    ```

    b. **Copie a URL HTTPS** da saída do
    [ngrok](https://www.corbado.com/blog/multi-device-passkey-login-corbado-ngrok) (ex:
    `https://string-aleatoria.ngrok.io`). c. **Crie um arquivo `.env.local`** e defina a
    URL:

    ```
    NEXT_PUBLIC_BASE_URL=https://<sua-url-ngrok>
    ```

5. **Execute a Aplicação:**

    ```bash
    npm run dev
    ```

    Abra seu navegador em `http://localhost:3000/issue`. Agora você pode preencher o
    formulário, e o código QR gerado apontará corretamente para sua URL pública do
    [ngrok](https://www.corbado.com/blog/multi-device-passkey-login-corbado-ngrok), permitindo que sua wallet
    móvel se conecte e receba a credencial.

### 5.2 A Importância do HTTPS e do `ngrok`

Os protocolos de credenciais digitais são construídos com a segurança como principal
prioridade. Por esse motivo, as wallets quase sempre se recusarão a se conectar a um
emissor por uma conexão insegura (`http://`). Todo o processo depende de uma conexão
**HTTPS** segura, que é habilitada por um **certificado SSL**.

Um serviço de túnel como o `ngrok` resolve ambos os problemas criando uma URL HTTPS
pública e segura (com um certificado SSL válido) que encaminha todo o tráfego para seu
servidor de desenvolvimento local.\
As wallets exigem HTTPS e se recusarão a se conectar a endpoints inseguros (`http://`).
Esta é uma ferramenta essencial para testar qualquer serviço web que precise interagir com
dispositivos móveis ou webhooks externos.

### 5.3 O que está Fora do Escopo deste Tutorial

Este exemplo é intencionalmente focado no fluxo principal de emissão para facilitar o
entendimento. Os seguintes tópicos são considerados fora do escopo:

- **Segurança Pronta para Produção:** O emissor é para fins educacionais. Um sistema de
  produção exigiria um Sistema de Gerenciamento de Chaves (KMS) seguro em vez de armazenar
  chaves em um banco de dados, tratamento robusto de erros, limitação de taxa e registro
  de auditoria abrangente.
- **Revogação de Credenciais:** Este guia não implementa um mecanismo para revogar
  credenciais emitidas.\
  Embora o esquema inclua uma flag `revoked` para uso futuro, nenhuma lógica de revogação
  é fornecida aqui.
- **Fluxo de Código de Autorização:** Focamos exclusivamente no fluxo
  `pre-authorized_code`. Uma implementação completa do fluxo `authorization_code` exigiria
  uma tela de consentimento do usuário e uma lógica [OAuth 2.0](https://www.corbado.com/glossary/oauth2) mais
  complexa.
- **Gerenciamento de Usuários:** O guia não inclui nenhuma
  [autenticação](https://www.corbado.com/pt/blog/passkeys-revolut) ou gerenciamento de usuários para o próprio
  emissor. Presume-se que o usuário já está autenticado e autorizado a receber uma
  credencial.

## 6. Conclusão

É isso! Com algumas páginas de código, agora temos um emissor de credenciais digitais
completo, de ponta a ponta, que:

1. Fornece um frontend amigável para solicitar credenciais.
2. Implementa o fluxo completo `pre-authorized_code` do OpenID4VCI.
3. Expõe todos os endpoints de descoberta necessários para a interoperabilidade da wallet.
4. Gera e assina uma Credencial Verificável JWT segura e em conformidade com os padrões.

Embora este guia forneça uma base sólida, um emissor pronto para produção exigiria
recursos adicionais como gerenciamento robusto de chaves, armazenamento persistente em vez
de armazenamentos em memória, revogação de credenciais e fortalecimento abrangente da
segurança.\
A compatibilidade da wallet também varia; a Sphereon Wallet é recomendada para testes, mas
outras wallets podem não suportar o fluxo pré-autorizado como implementado aqui. No
entanto, os blocos de construção principais e o fluxo de interação permaneceriam os
mesmos. Seguindo esses padrões, você pode construir um emissor seguro e interoperável para
qualquer tipo de credencial digital.

## 7. Recursos

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

- **Repositório do Projeto:**
    - [Código Fonte Completo no GitHub](https://github.com/corbado/digital-credentials-example)

- **Especificações Principais:**
    - [OpenID for Verifiable Credential Issuance (OpenID4VCI)](https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html):
      O protocolo de emissão principal.
    - [W3C Verifiable Credentials Data Model](https://www.w3.org/TR/vc-data-model/): O
      padrão fundamental para VCs.
    - [The `did:web` Method](https://w3c-ccg.github.io/did-method-web/): O método DID
      usado para a chave pública do nosso emissor.

- **Ferramentas:**
    - [Sphereon Wallet](https://sphereon.com/wallet/): A wallet de teste usada neste guia.
    - ngrok: Para criar um túnel seguro para seu ambiente de desenvolvimento local.

- **Bibliotecas:**
    - Next.js: O framework React para construir o frontend e o backend.
    - [jose](https://github.com/panva/jose): Para criar e assinar JSON Web Tokens (JWTs).
    - [mysql2](https://github.com/sidorares/node-mysql2): O cliente MySQL para
      [Node.js](https://www.corbado.com/blog/nodejs-passkeys).
