Emissão de credenciais OpenID4VCI explicada: da oferta à credencial aceite
OpenID4VCI, OpenID for Verifiable Credential Issuance, define como uma wallet solicita e recebe uma credencial de um issuer. Uma única emissão percorre vários passos distintos antes de a wallet ter efetivamente uma credencial utilizável, e cada um tem o seu próprio modo de falha. Esta página percorre esse ciclo de vida na íntegra, com exemplos práticos originais.
Duas formas de começar: código pré-autorizado e código de autorização
A emissão começa muitas vezes a partir de uma Credential Offer enviada pelo issuer, e o grant que esta indica decide como a wallet é autorizada. Uma wallet também pode iniciar a emissão por conta própria, sem qualquer oferta, usando o fluxo de código de autorização. Ambos os fluxos terminam no mesmo ponto: a wallet na posse de um access token que pode usar para solicitar a credencial.
Fluxo de código pré-autorizado
1. Issuer
Já conhece o titular e entrega uma Credential Offer com um pre-authorized_code
2. Wallet
Resgata o código no token endpoint, opcionalmente com um código de transação
3. Wallet
Solicita a credencial com uma prova de posse da sua chave
Fluxo de código de autorização
1. Wallet
Digitaliza uma Credential Offer que indica um grant authorization_code, ou inicia o fluxo por conta própria sem oferta
2. Authorization Server
Conduz o titular através do login e do consentimento, e depois emite um código
3. Wallet
Troca o código por um token e depois solicita a credencial
O ciclo de vida completo de uma oferta de credencial
A especificação não define estados nomeados, mas um fluxo iniciado pelo issuer em que a credencial é emitida de imediato é o mais fácil de seguir, como na sequência abaixo. Cada passo pode falhar à sua maneira, e uma implementação de wallet tem de lidar com esses caminhos, não apenas com o cenário ideal.
Exemplo prático: um certificado de inspeção para uma frota de transportes
Uma entidade de inspeção de veículos emite um certificado de inspeção para a business wallet de uma transportadora após uma inspeção de rotina. O inspetor já autenticou o gestor de frota no ponto de inspeção, pelo que o issuer usa o fluxo de código pré-autorizado. Os passos abaixo seguem esta única emissão, desde a oferta até à credencial aceite.
O exemplo mostra o OpenID4VCI base. Implementações de elevada garantia, como a EUDI Wallet, seguem o perfil HAIP sobre este, que acrescenta access tokens vinculados a DPoP, wallet attestation no token endpoint e key attestation para as chaves da credencial. Estes aspetos são omitidos aqui para manter cada passo fácil de acompanhar.
1. Oferta de credencial
O terminal da entidade de inspeção mostra um código QR. Este contém um URI que começa por openid-credential-offer:// e transporta a oferta abaixo, codificada em URL num parâmetro credential_offer, ou um credential_offer_uri a partir do qual a wallet a obtém. A wallet digitaliza-o e lê qual a credencial oferecida e como obtê-la.
Issuer
Mostra um código QR com uma Credential Offer
Indica a configuração da credencial e um grant pre-authorized_code
{
"credential_issuer": "https://issuer.fleetinspect.example",
"credential_configuration_ids": ["roadworthiness_certificate"],
"grants": {
"urn:ietf:params:oauth:grant-type:pre-authorized_code": {
"pre-authorized_code": "fi-8f2c0b3a",
"tx_code": {
"length": 6,
"input_mode": "numeric",
"description": "Enter the code sent to your phone by text message"
}
}
}
}2. Descoberta de metadados do issuer
Antes de solicitar seja o que for, a wallet obtém os metadados do issuer para saber o que o roadworthiness_certificate contém e quais os endpoints a chamar. Os metadados não listam authorization servers separados, pelo que o issuer é o seu próprio authorization server, e a wallet lê o token endpoint a partir dos metadados desse servidor.
Wallet
GET /.well-known/openid-credential-issuer
Conhece o formato da credencial, as claims e os tipos de prova aceites, além dos endpoints de nonce, credential, deferred e notification
Metadados do credential issuer (excerto)
{
"credential_issuer": "https://issuer.fleetinspect.example",
"nonce_endpoint": "https://issuer.fleetinspect.example/nonce",
"credential_endpoint": "https://issuer.fleetinspect.example/credential",
"deferred_credential_endpoint": "https://issuer.fleetinspect.example/deferred",
"notification_endpoint": "https://issuer.fleetinspect.example/notify",
"credential_configurations_supported": {
"roadworthiness_certificate": {
"format": "dc+sd-jwt",
"vct": "https://fleetinspect.example/vct/roadworthiness",
"cryptographic_binding_methods_supported": ["jwk"],
"credential_signing_alg_values_supported": ["ES256"],
"proof_types_supported": {
"jwt": { "proof_signing_alg_values_supported": ["ES256"] }
},
"credential_metadata": {
"claims": [
{ "path": ["vehicle_registration"] },
{ "path": ["inspection_result"] },
{ "path": ["valid_until"] }
]
}
}
}
}Metadados do authorization server (excerto), de /.well-known/oauth-authorization-server
{
"issuer": "https://issuer.fleetinspect.example",
"token_endpoint": "https://issuer.fleetinspect.example/token",
"pre-authorized_grant_anonymous_access_supported": true
}3. Pedido de token
A wallet resgata o código pré-autorizado no token endpoint, juntamente com o código de transação que a entidade de inspeção enviou ao telemóvel do gestor de frota. Enviar esse código por um segundo canal significa que alguém que fotografe o código QR por cima do ombro continua a não conseguir resgatá-lo.
Wallet
POST /token
Envia o pre-authorized_code e o tx_code, recebe um access token limitado a esta oferta
POST /token HTTP/1.1 Host: issuer.fleetinspect.example Content-Type: application/x-www-form-urlencoded grant_type=urn:ietf:params:oauth:grant-type:pre-authorized_code &pre-authorized_code=fi-8f2c0b3a &tx_code=482913
Resposta
{
"access_token": "fi-at-3d91e0",
"token_type": "Bearer",
"expires_in": 86400
}4. Prova de posse
Como os metadados listam um nonce_endpoint, a wallet obtém primeiro um c_nonce recente aí. Depois prova que possui a chave privada a que a credencial será vinculada, assinando um JWT de prova sobre o identificador do issuer e esse c_nonce.
Wallet
POST /nonce, depois assina um JWT de prova com a chave a que a credencial será vinculada
Vincula a credencial a essa chave, não apenas a quem possui o access token
POST /nonce HTTP/1.1 Host: issuer.fleetinspect.example
Resposta
{
"c_nonce": "fi-nonce-77aa"
}A wallet constrói agora o JWT de prova a partir de dois objetos JSON, um header e um payload, e assina-os com a chave privada a que a credencial será vinculada.
Header: o que é este JWT e qual chave o assinou
{
"typ": "openid4vci-proof+jwt",
"alg": "ES256",
"jwk": { "kty": "EC", "crv": "P-256", "x": "...", "y": "..." }
}typ: marca isto como uma key proof OpenID4VCI, para não poder ser confundida com qualquer outro tipo de JWTalg: o algoritmo de assinatura, um dos que o issuer listou em proof_signing_alg_values_supportedjwk: a chave pública a que a credencial será vinculada; o issuer verifica a assinatura em relação a esta
Payload: para quem é a prova e quando foi criada
{
"aud": "https://issuer.fleetinspect.example",
"iat": 1789376400,
"nonce": "fi-nonce-77aa"
}aud: o identificador do issuer, para que a prova não possa ser reutilizada noutro issueriat: o momento em que a prova foi criada, em segundos desde 1970nonce: o c_nonce do nonce endpoint, que demonstra que a prova é recente
Resultado assinado
O header e o payload são cada um codificados em base64url e unidos com um ponto. A wallet assina essa string com a sua chave privada e junta a assinatura codificada em base64url após um segundo ponto. A string resultante é o JWT de prova que a wallet envia no pedido de credencial no passo 5.
base64url(header) . base64url(payload) . base64url(signature) eyJ0eXAiOiJvcGVuaWQ0dmNpLXByb29mK2p3dCIs... .eyJhdWQiOiJodHRwczovL2lzc3Vlci5mbGVldGluc3BlY3QuZXhhbXBsZSIs... .<ES256 signature>
5. Pedido de credencial
A wallet chama o credential endpoint com o access token e a prova, e o issuer gera e devolve a credencial assinada.
Wallet
POST /credential
Envia o access token, o id de configuração e o JWT de prova, recebe a credencial assinada e um notification_id
POST /credential HTTP/1.1
Host: issuer.fleetinspect.example
Content-Type: application/json
Authorization: Bearer fi-at-3d91e0
{
"credential_configuration_id": "roadworthiness_certificate",
"proofs": {
"jwt": ["<proof JWT from step 4>"]
}
}Resposta
{
"credentials": [
{ "credential": "<issuer-signed SD-JWT VC>" }
],
"notification_id": "fi-notif-9012"
}Todo o fluxo num relance
Este diagrama de sequência reúne os cinco passos do exemplo prático, desde a oferta até à notificação. As setas contínuas são pedidos, as tracejadas são respostas, e a seta pontilhada é o código de transação a viajar fora do protocolo por SMS.
Wallet
Business wallet da transportadora
Authorization Server
Operado pelo próprio issuer neste exemplo
Credential Issuer
Entidade de inspeção de veículos
- Credential Issuer para Wallet: Credential Offer, apresentada como código QR
- Credential Issuer para Wallet: tx_code, enviado por SMS ao telemóvel do gestor de frota
- Wallet para Credential Issuer: GET /.well-known/openid-credential-issuer
- Credential Issuer para Wallet: metadados do credential issuer
- Wallet para Authorization Server: GET /.well-known/oauth-authorization-server
- Authorization Server para Wallet: metadados do authorization server
- Wallet para Authorization Server: POST /token: pre-authorized_code, tx_code
- Authorization Server para Wallet: access_token
- Wallet para Credential Issuer: POST /nonce
- Credential Issuer para Wallet: c_nonce
- Wallet: assina o JWT de prova
- Wallet para Credential Issuer: POST /credential: access token, provas
- Credential Issuer para Wallet: credentials, notification_id
- Wallet: valida e armazena
- Wallet para Credential Issuer: POST /notify: credential_accepted
- Credential Issuer para Wallet: 204 No Content
Quando a credencial ainda não está pronta: emissão diferida
O exemplo acima assume que o resultado da inspeção já é final. Se, em vez disso, a entidade de inspeção precisar de escalar um resultado duvidoso para um inspetor sénior, o credential endpoint não pode devolver a credencial de imediato, pelo que diferi a emissão.
Emissão imediata
O credential endpoint devolve a credencial assinada na mesma resposta do pedido.
Emissão diferida
O credential endpoint responde antes com HTTP 202, um transaction_id e um intervalo. A wallet consulta o deferred credential endpoint com esse id, aguardando pelo menos o número de segundos do intervalo entre pedidos, até a credencial estar pronta.
Resposta diferida de /credential
HTTP/1.1 202 Accepted
Content-Type: application/json
{
"transaction_id": "fi-tx-55c2",
"interval": 900
}Consultar /deferred até estar pronta
POST /deferred HTTP/1.1
Host: issuer.fleetinspect.example
Content-Type: application/json
Authorization: Bearer fi-at-3d91e0
{ "transaction_id": "fi-tx-55c2" }
// While the review is open: 202 with the same transaction_id and interval.
// Once approved: 200 with the credentials, optionally with a notification_id.
// If the review rejects the result: a credential_request_denied error.Fechar o ciclo: o notification endpoint
Após a emissão, a wallet pode informar o issuer do que aconteceu à credencial, usando o notification_id da resposta da credencial. As wallets não são obrigadas a enviar estas notificações e a entrega não é garantida, pelo que um issuer não pode interpretar uma notificação em falta como significando seja o que for.
Wallet
Envia um evento para o notification_endpoint do issuer para o notification_id recebido, que abrange todas as credenciais dessa resposta
Armazenada na wallet
A emissão falhou por qualquer outro motivo, por exemplo a credencial não passou na validação
A emissão falhou por causa do titular, por exemplo este recusou armazená-la
POST /notify HTTP/1.1
Host: issuer.fleetinspect.example
Content-Type: application/json
Authorization: Bearer fi-at-3d91e0
{
"notification_id": "fi-notif-9012",
"event": "credential_accepted"
}Termos relacionados
Perguntas frequentes
Como sabe uma wallet qual o tipo de grant a usar?
A Credential Offer indica o grant no seu objeto grants. authorization_code está presente quando o issuer pretende que o titular faça login como parte do fluxo. pre-authorized_code está presente quando o titular já estava autenticado no canal onde a oferta foi criada, por exemplo pelo inspetor no ponto de inspeção do exemplo abaixo. Uma oferta pode listar ambos, e a wallet escolhe então um. Se a oferta não tiver qualquer objeto grants, a wallet consulta os tipos de grant suportados pelo authorization server nos seus metadados.
Porque é que a wallet obtém os metadados do issuer antes de pedir seja o que for?
A Credential Offer indica apenas os credential_configuration_ids, o URL do issuer e os grants. Os metadados do issuer, disponibilizados num caminho well-known, descrevem cada configuração: o seu formato, as suas claims e os tipos de prova aceites, além dos endpoints de nonce, credential, deferred e notification. Indicam também qual authorization server usar, e os metadados desse servidor fornecem o token endpoint. Sem ambos, a wallet não saberia como construir pedidos válidos nem o que mostrar ao titular antes do consentimento.
O que prova realmente a prova de posse?
Prova que a wallet que solicita a credencial possui a chave privada a que a credencial será vinculada, não apenas que tem um access token válido. A wallet assina um JWT de prova sobre o identificador do issuer e um c_nonce recente do nonce endpoint do issuer, usando essa chave. O issuer incorpora a chave pública correspondente na credencial. Um verifier que exija vinculação de chave pede ao titular que assine novamente com a mesma chave ao apresentar a credencial, pelo que uma credencial copiada sem a chave não passa nessa verificação.
Porque é que um issuer diferiria a emissão em vez de devolver a credencial de imediato?
Algumas verificações que o issuer realiza antes de gerar uma credencial não conseguem concluir-se dentro de um único pedido HTTP, por exemplo uma revisão manual ou uma chamada a um registo externo lento. A emissão diferida permite ao credential endpoint responder de imediato com um transaction_id em vez de bloquear a ligação, e a wallet consulta o endpoint diferido com esse id até a verificação terminar e a credencial estar pronta a recolher.
O notification endpoint é obrigatório para um issuer implementar?
Não. É opcional para os issuers, e as wallets também não são obrigadas a usá-lo. Quando ambos o suportam, o issuer fica a saber o que aconteceu após a emissão: as credenciais foram armazenadas (credential_accepted), o titular interrompeu a emissão, por exemplo recusando armazená-las (credential_deleted), ou falhou por outro motivo (credential_failure). A entrega não é garantida, pelo que um issuer deve tratar uma notificação como informação útil, nunca como um registo fiável, e não pode interpretar nada a partir de uma notificação em falta.
Fontes
Esta página tem caráter meramente informativo e não constitui aconselhamento jurídico. Para orientações oficiais, consulte diretamente a OpenID Foundation e a Comissão Europeia.