Emiterea acreditărilor OpenID4VCI explicată: de la ofertă la acreditarea acceptată
OpenID4VCI, OpenID for Verifiable Credential Issuance, definește modul în care un portofel solicită și primește o acreditare de la un emitent. O singură emitere parcurge mai mulți pași distincți înainte ca portofelul să dețină efectiv o acreditare utilizabilă, iar fiecare are propriul mod de eșec. Această pagină parcurge integral acest ciclu de viață, cu exemple practice originale.
Două moduri de a începe: cod preautorizat și cod de autorizare
Emiterea începe adesea de la o Credential Offer trimisă de emitent, iar grantul indicat de aceasta decide cum este autorizat portofelul. Un portofel poate începe și el emiterea, fără nicio ofertă, folosind fluxul cu cod de autorizare. Ambele fluxuri se termină în același loc: portofelul deține un token de acces pe care îl poate folosi pentru a solicita acreditarea.
Fluxul cu cod preautorizat
1. Emitent
Cunoaște deja deținătorul, emite o Credential Offer cu un pre-authorized_code
2. Portofel
Utilizează codul la punctul final de token, opțional cu un cod de tranzacție
3. Portofel
Solicită acreditarea cu o dovadă de posesie a cheii sale
Fluxul cu cod de autorizare
1. Portofel
Scanează o Credential Offer care indică un grant authorization_code, sau începe fluxul singur, fără nicio ofertă
2. Server de autorizare
Ghidează deținătorul prin autentificare și consimțământ, apoi emite un cod
3. Portofel
Schimbă codul cu un token, apoi solicită acreditarea
Ciclul de viață complet al unei oferte de acreditare
Specificația nu definește stări numite, dar un flux inițiat de emitent, în care acreditarea este emisă imediat, este cel mai ușor de urmărit ca secvența de mai jos. Fiecare pas poate eșua în felul său, iar o implementare de portofel trebuie să gestioneze acele căi, nu doar pe cea fericită.
Exemplu practic: un certificat de inspecție tehnică pentru o flotă de transport
Un organism de inspecție tehnică auto emite un certificat de inspecție tehnică către portofelul de afaceri al unei firme de transport, după o inspecție de rutină. Inspectorul l-a autentificat deja pe managerul de flotă la punctul de inspecție, așa că emitentul folosește fluxul cu cod preautorizat. Pașii de mai jos urmăresc această singură emitere, de la ofertă până la acreditarea acceptată.
Exemplul prezintă versiunea de bază a OpenID4VCI. Implementările cu asigurare ridicată, precum EUDI Wallet, aplică peste aceasta profilul HAIP, care adaugă tokenuri de acces legate prin DPoP, atestare a portofelului la punctul final de token și atestare a cheilor pentru cheile de acreditare. Acestea sunt omise aici pentru ca fiecare pas să rămână ușor de citit.
1. Oferta de acreditare
Terminalul organismului de inspecție afișează un cod QR. Acesta conține un URI care începe cu openid-credential-offer:// și transportă oferta de mai jos, codificată URL, într-un parametru credential_offer, sau un credential_offer_uri de la care portofelul o preia. Portofelul scanează codul și citește ce acreditare este oferită și cum să o obțină.
Emitent
Afișează un cod QR cu o Credential Offer
Indică configurația acreditării și un 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. Descoperirea metadatelor emitentului
Înainte de a solicita ceva, portofelul obține metadatele emitentului pentru a afla ce conține roadworthiness_certificate și ce puncte finale să apeleze. Metadatele nu listează servere de autorizare separate, așa că emitentul este propriul său server de autorizare, iar portofelul citește punctul final de token din metadatele acelui server.
Portofel
GET /.well-known/openid-credential-issuer
Află formatul acreditării, atributele și tipurile de dovezi acceptate, plus punctele finale pentru nonce, acreditare, amânare și notificare
Metadatele emitentului de acreditări (extras)
{
"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"] }
]
}
}
}
}Metadatele serverului de autorizare (extras), de la /.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. Cererea de token
Portofelul utilizează codul preautorizat la punctul final de token, împreună cu codul de tranzacție pe care organismul de inspecție l-a trimis pe telefonul managerului de flotă. Trimiterea acelui cod pe un al doilea canal înseamnă că cineva care fotografiază codul QR peste umăr tot nu îl poate folosi.
Portofel
POST /token
Trimite pre-authorized_code și tx_code, primește un token de acces limitat la această ofertă
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
Răspuns
{
"access_token": "fi-at-3d91e0",
"token_type": "Bearer",
"expires_in": 86400
}4. Dovada de posesie
Deoarece metadatele listează un nonce_endpoint, portofelul obține mai întâi de acolo un c_nonce proaspăt. Apoi dovedește că deține cheia privată de care va fi legată acreditarea, semnând un proof JWT peste identificatorul emitentului și acel c_nonce.
Portofel
POST /nonce, apoi semnează un proof JWT cu cheia de care va fi legată acreditarea
Leagă acreditarea de acea cheie, nu doar de cine deține tokenul de acces
POST /nonce HTTP/1.1 Host: issuer.fleetinspect.example
Răspuns
{
"c_nonce": "fi-nonce-77aa"
}Portofelul construiește acum proof JWT din două obiecte JSON, un antet și o sarcină utilă, și le semnează cu cheia privată de care va fi legată acreditarea.
Antet: ce este acest JWT și ce cheie l-a semnat
{
"typ": "openid4vci-proof+jwt",
"alg": "ES256",
"jwk": { "kty": "EC", "crv": "P-256", "x": "...", "y": "..." }
}typ: marchează acest lucru ca o dovadă de cheie OpenID4VCI, astfel încât să nu poată fi confundat cu niciun alt tip de JWTalg: algoritmul de semnare, unul dintre cele listate de emitent în proof_signing_alg_values_supportedjwk: cheia publică de care va fi legată acreditarea; emitentul verifică semnătura pe baza acesteia
Sarcină utilă: pentru cine este dovada și când a fost creată
{
"aud": "https://issuer.fleetinspect.example",
"iat": 1789376400,
"nonce": "fi-nonce-77aa"
}aud: identificatorul emitentului, astfel încât dovada să nu poată fi reluată la un alt emitentiat: momentul la care a fost creată dovada, în secunde de la 1970nonce: c_nonce de la punctul final de nonce, care arată că dovada este proaspătă
Rezultatul semnat
Antetul și sarcina utilă sunt fiecare codificate base64url și unite printr-un punct. Portofelul semnează acel șir cu cheia sa privată și adaugă semnătura codificată base64url după un al doilea punct. Șirul rezultat este proof JWT pe care portofelul îl trimite în cererea de acreditare de la pasul 5.
base64url(header) . base64url(payload) . base64url(signature) eyJ0eXAiOiJvcGVuaWQ0dmNpLXByb29mK2p3dCIs... .eyJhdWQiOiJodHRwczovL2lzc3Vlci5mbGVldGluc3BlY3QuZXhhbXBsZSIs... .<ES256 signature>
5. Cererea de acreditare
Portofelul apelează punctul final de acreditare cu tokenul de acces și dovada, iar emitentul generează și returnează acreditarea semnată.
Portofel
POST /credential
Trimite tokenul de acces, identificatorul configurației și proof JWT, primește acreditarea semnată și un 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>"]
}
}Răspuns
{
"credentials": [
{ "credential": "<issuer-signed SD-JWT VC>" }
],
"notification_id": "fi-notif-9012"
}Întregul flux dintr-o privire
Această diagramă de secvență reunește cei cinci pași ai exemplului practic, de la ofertă până la notificare. Săgețile continue sunt cereri, cele întrerupte sunt răspunsuri, iar săgeata punctată este codul de tranzacție care circulă în afara protocolului, printr-un mesaj text.
Portofel
Portofelul de afaceri al firmei de transport
Server de autorizare
Administrat chiar de emitent în acest exemplu
Emitent de acreditări
Organism de inspecție tehnică auto
- Emitent de acreditări către Portofel: Credential Offer, afișată ca cod QR
- Emitent de acreditări către Portofel: tx_code, trimis prin SMS pe telefonul managerului de flotă
- Portofel către Emitent de acreditări: GET /.well-known/openid-credential-issuer
- Emitent de acreditări către Portofel: metadatele emitentului de acreditări
- Portofel către Server de autorizare: GET /.well-known/oauth-authorization-server
- Server de autorizare către Portofel: metadatele serverului de autorizare
- Portofel către Server de autorizare: POST /token: pre-authorized_code, tx_code
- Server de autorizare către Portofel: access_token
- Portofel către Emitent de acreditări: POST /nonce
- Emitent de acreditări către Portofel: c_nonce
- Portofel: semnează proof JWT
- Portofel către Emitent de acreditări: POST /credential: token de acces, dovezi
- Emitent de acreditări către Portofel: credentials, notification_id
- Portofel: validează și salvează
- Portofel către Emitent de acreditări: POST /notify: credential_accepted
- Emitent de acreditări către Portofel: 204 No Content
Când acreditarea nu este încă gata: emiterea amânată
Exemplul de mai sus presupune că rezultatul inspecției este deja final. Dacă organismul de inspecție trebuie în schimb să escaladeze un rezultat la limită către un inspector superior, punctul final de acreditare nu poate returna acreditarea imediat, așa că amână emiterea.
Emitere imediată
Punctul final de acreditare returnează acreditarea semnată în același răspuns ca cererea.
Emitere amânată
Punctul final de acreditare răspunde cu HTTP 202, un transaction_id și un interval, în loc de acreditare. Portofelul interoghează deferred_credential_endpoint cu acel identificator, așteptând cel puțin numărul de secunde indicat de interval între cereri, până când acreditarea este gata.
Răspuns amânat de la /credential
HTTP/1.1 202 Accepted
Content-Type: application/json
{
"transaction_id": "fi-tx-55c2",
"interval": 900
}Interogarea /deferred până este gata
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.Închiderea buclei: punctul final de notificare
După emitere, portofelul poate anunța emitentul ce s-a întâmplat cu acreditarea, folosind notification_id din răspunsul de acreditare. Portofelele nu sunt obligate să trimită aceste notificări, iar livrarea nu este garantată, așa că un emitent nu poate interpreta în niciun fel absența unei notificări.
Portofel
Trimite un eveniment către notification_endpoint al emitentului pentru notification_id primit, care acoperă fiecare acreditare din acel răspuns
Salvată în portofel
Emiterea a eșuat din alt motiv, de exemplu acreditarea nu a trecut validarea
Emiterea a eșuat din cauza deținătorului, de exemplu a refuzat să o salveze
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"
}Termeni asociați
Întrebări frecvente
De unde știe portofelul ce tip de grant să folosească?
Credential Offer indică grantul în obiectul său grants. authorization_code apare atunci când emitentul dorește ca deținătorul să se autentifice ca parte a fluxului. pre-authorized_code apare atunci când deținătorul era deja autentificat pe canalul pe care a fost creată oferta, de exemplu de către inspector la punctul de inspecție din exemplul practic de mai jos. O ofertă poate include ambele, iar portofelul alege apoi una dintre ele. Dacă oferta nu are deloc un obiect grants, portofelul verifică în metadate ce tipuri de granturi sunt acceptate de serverul de autorizare.
De ce portofelul obține metadatele emitentului înainte de a solicita ceva?
Credential Offer indică doar credential_configuration_ids, adresa URL a emitentului și grants. Metadatele emitentului, furnizate la o cale bine cunoscută, sunt cele care descriu fiecare configurație: formatul, atributele și tipurile de dovezi acceptate, plus punctele finale pentru nonce, acreditare, amânare și notificare. Ele indică și ce server de autorizare trebuie folosit, iar metadatele acelui server oferă punctul final de token. Fără ambele, portofelul nu ar ști cum să construiască cereri valide sau ce să îi arate deținătorului înainte de a-și da consimțământul.
Ce dovedește de fapt dovada de posesie?
Dovedește că portofelul care solicită acreditarea deține cheia privată de care va fi legată acreditarea, nu doar că are un token de acces valid. Portofelul semnează un proof JWT peste identificatorul emitentului și un c_nonce proaspăt de la punctul final de nonce al emitentului, folosind acea cheie. Emitentul include cheia publică corespunzătoare în acreditare. Un verificator care solicită legarea de cheie îi cere deținătorului să semneze din nou cu aceeași cheie la prezentare, astfel încât o acreditare copiată fără cheie nu trece acea verificare.
De ce ar amâna un emitent emiterea în loc să returneze acreditarea imediat?
Unele verificări pe care emitentul le efectuează înainte de a genera o acreditare nu se pot finaliza în cadrul unei singure cereri HTTP, de exemplu o revizuire manuală sau un apel către un registru extern lent. Emiterea amânată permite punctului final de acreditare să răspundă imediat cu un transaction_id în loc să blocheze conexiunea, iar portofelul interoghează punctul final amânat cu acel identificator până când verificarea se finalizează și acreditarea este gata de preluat.
Este punctul final de notificare obligatoriu pentru ca un emitent să îl implementeze?
Nu. Este opțional pentru emitenți, iar portofelele nu sunt nici ele obligate să îl folosească. Când ambele îl suportă, emitentul află ce s-a întâmplat după emitere: acreditările au fost salvate (credential_accepted), deținătorul a oprit emiterea, de exemplu refuzând să le salveze (credential_deleted), sau a eșuat din alt motiv (credential_failure). Livrarea nu este garantată, așa că un emitent ar trebui să trateze o notificare ca informație utilă, niciodată ca o evidență de încredere, și nu poate deduce nimic dintr-una lipsă.
Surse
Această pagină are caracter informativ și nu constituie consultanță juridică. Pentru îndrumări autorizate, contactați direct OpenID Foundation și Comisia Europeană.