Spring til hovedindhold

OpenID4VCI-udstedelse af legitimation forklaret: fra tilbud til accepteret legitimation

OpenID4VCI, OpenID for Verifiable Credential Issuance, definerer, hvordan en wallet anmoder om og modtager en legitimation fra en issuer. En enkelt udstedelse gennemløber flere adskilte trin, før wallet reelt besidder en brugbar legitimation, og hvert trin har sin egen fejltype. Denne side gennemgår hele det forløb i detaljer, med originale gennemarbejdede eksempler.

To måder at starte på: forhåndsautoriseret kode og autorisationskode

Udstedelse starter ofte fra et Credential Offer sendt af issueren, og den grant, det angiver, afgør, hvordan wallet bliver autoriseret. En wallet kan også selv starte udstedelsen uden noget tilbud ved brug af autorisationskodeflowet. Begge flows ender samme sted: wallet besidder et access token, den kan bruge til at anmode om legitimationen.

Flow med forhåndsautoriseret kode

1. Issuer

Kender allerede indehaveren og udleverer et Credential Offer med en pre-authorized_code

2. Wallet

Indløser koden hos token endpoint, eventuelt sammen med en transaktionskode

3. Wallet

Anmoder om legitimationen med et bevis på besiddelse af sin nøgle

Flow med autorisationskode

1. Wallet

Scanner et Credential Offer, der angiver et authorization_code-grant, eller starter selv flowet uden et tilbud

2. Authorization Server

Fører indehaveren gennem login og samtykke og udsteder derefter en kode

3. Wallet

Bytter koden til et token og anmoder derefter om legitimationen

Hele livscyklussen for et legitimationstilbud

Specifikationen definerer ikke navngivne tilstande, men et issuer-initieret flow, hvor legitimationen udstedes med det samme, er nemmest at følge, som i sekvensen nedenfor. Hvert trin kan fejle på sin egen måde, og en wallet-implementering skal håndtere disse veje, ikke kun den ideelle.

Tilbud oprettet
QR-kode eller link åbner wallet
Tilbud modtaget
grant indløst
Token modtaget
nonce og bevis på nøglebesiddelse
Legitimation anmodet
legitimation udstedt
Legitimation udstedt
wallet validerer og gemmer
Legitimation accepteret
Hvor det kan fejle, i den rækkefølge trinnene kører:
Koden udløb før indløsning
Token-anmodning afvist
Legitimationsanmodning afvist
Legitimation ikke gemt

Gennemarbejdet eksempel: et synscertifikat for en vognmandsflåde

En køretøjssynsvirksomhed udsteder et synscertifikat til et vognmandsfirmas business wallet efter et rutinesyn. Synsinspektøren har allerede autentificeret flådelederen på synsstedet, så issueren bruger flowet med forhåndsautoriseret kode. Trinnene nedenfor følger denne ene udstedelse fra tilbud til accepteret legitimation.

Eksemplet viser grundlæggende OpenID4VCI. Løsninger med høj sikringsgrad, som EUDI Wallet, følger HAIP-profilen oven på dette, som tilføjer DPoP-bundne access tokens, wallet attestation hos token endpoint og key attestation for legitimationens nøgler. Det er udeladt her for at holde hvert trin let at følge.

1. Legitimationstilbud

Synsvirksomhedens terminal viser en QR-kode. Den indeholder en URI, der starter med openid-credential-offer://, og som bærer tilbuddet nedenfor, URL-kodet i en credential_offer-parameter, eller en credential_offer_uri, som wallet henter det fra. Wallet scanner den og læser, hvilken legitimation der tilbydes, og hvordan den hentes.

Issuer

Viser en QR-kode med et Credential Offer

Angiver legitimationskonfigurationen og et pre-authorized_code-grant

{
  "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. Opdagelse af issuerens metadata

Før wallet anmoder om noget som helst, henter den issuerens metadata for at finde ud af, hvad roadworthiness_certificate indeholder, og hvilke endpoints der skal kaldes. Metadataen lister ingen separate authorization servers, så issueren er sin egen authorization server, og wallet læser token endpoint fra den servers metadata.

Wallet

GET /.well-known/openid-credential-issuer

Lærer legitimationsformatet, claims og accepterede prooftyper, samt endpoints for nonce, credential, deferred og notification

Metadata for credential issuer (uddrag)

{
  "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"] }
        ]
      }
    }
  }
}

Metadata for authorization server (uddrag), fra /.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. Token-anmodning

Wallet indløser den forhåndsautoriserede kode hos token endpoint sammen med transaktionskoden, som synsvirksomheden sendte til flådelederens telefon. At sende koden over en anden kanal betyder, at en person, der fotograferer QR-koden over skulderen, stadig ikke kan indløse den.

Wallet

POST /token

Sender pre-authorized_code og tx_code, modtager et access token begrænset til dette tilbud

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

Svar

{
  "access_token": "fi-at-3d91e0",
  "token_type": "Bearer",
  "expires_in": 86400
}

4. Bevis på nøglebesiddelse

Fordi metadataen lister en nonce_endpoint, henter wallet først en frisk c_nonce der. Den beviser derefter, at den besidder den private nøgle, som legitimationen bliver bundet til, ved at signere en proof-JWT over issuer-identifikatoren og den c_nonce.

Wallet

POST /nonce, signerer derefter en proof-JWT med den nøgle, legitimationen bliver bundet til

Binder legitimationen til den nøgle, ikke blot til den, der besidder access token

POST /nonce HTTP/1.1
Host: issuer.fleetinspect.example

Svar

{
  "c_nonce": "fi-nonce-77aa"
}

Wallet bygger nu proof-JWT'en ud fra to JSON-objekter, en header og en payload, og signerer dem med den private nøgle, legitimationen bliver bundet til.

Header: hvad denne JWT er, og hvilken nøgle der signerede den

{
  "typ": "openid4vci-proof+jwt",
  "alg": "ES256",
  "jwk": { "kty": "EC", "crv": "P-256", "x": "...", "y": "..." }
}
  • typ: markerer dette som en OpenID4VCI key proof, så den ikke kan forveksles med nogen anden slags JWT
  • alg: signeringsalgoritmen, en af dem issueren angav i proof_signing_alg_values_supported
  • jwk: den offentlige nøgle, legitimationen bliver bundet til; issueren tjekker signaturen mod den

Payload: hvem beviset gælder for, og hvornår det blev lavet

{
  "aud": "https://issuer.fleetinspect.example",
  "iat": 1789376400,
  "nonce": "fi-nonce-77aa"
}
  • aud: issuer-identifikatoren, så beviset ikke kan genbruges hos en anden issuer
  • iat: tidspunktet, hvor beviset blev oprettet, i sekunder siden 1970
  • nonce: c_nonce fra nonce endpoint, som viser, at beviset er frisk

Signeret resultat

Header og payload bliver hver base64url-kodet og sat sammen med et punktum. Wallet signerer den streng med sin private nøgle og tilføjer den base64url-kodede signatur efter et andet punktum. Den resulterende streng er den proof-JWT, wallet sender i legitimationsanmodningen i trin 5.

base64url(header) . base64url(payload) . base64url(signature)

eyJ0eXAiOiJvcGVuaWQ0dmNpLXByb29mK2p3dCIs...
  .eyJhdWQiOiJodHRwczovL2lzc3Vlci5mbGVldGluc3BlY3QuZXhhbXBsZSIs...
  .<ES256 signature>

5. Legitimationsanmodning

Wallet kalder credential endpoint med access token og beviset, og issueren udsteder og returnerer den signerede legitimation.

Wallet

POST /credential

Sender access token, konfigurations-id'et og proof-JWT'en, modtager den signerede legitimation og et 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>"]
  }
}

Svar

{
  "credentials": [
    { "credential": "<issuer-signed SD-JWT VC>" }
  ],
  "notification_id": "fi-notif-9012"
}

Hele forløbet på ét overblik

Dette sekvensdiagram samler de fem trin i det gennemarbejdede eksempel, fra tilbuddet til notifikationen. Massive pile er anmodninger, stiplede pile er svar, og den prikkede pil er transaktionskoden, der rejser uden for protokollen via sms.

Wallet

Vognmandsfirmaets business wallet

Authorization Server

Drives af issueren selv i dette eksempel

Credential Issuer

Køretøjssynsvirksomhed

  1. Credential Issuer til Wallet: Credential Offer, vist som QR-kode
  2. Credential Issuer til Wallet: tx_code, sendt til flådelederens telefon via sms
  3. Wallet til Credential Issuer: GET /.well-known/openid-credential-issuer
  4. Credential Issuer til Wallet: metadata for credential issuer
  5. Wallet til Authorization Server: GET /.well-known/oauth-authorization-server
  6. Authorization Server til Wallet: metadata for authorization server
  7. Wallet til Authorization Server: POST /token: pre-authorized_code, tx_code
  8. Authorization Server til Wallet: access_token
  9. Wallet til Credential Issuer: POST /nonce
  10. Credential Issuer til Wallet: c_nonce
  11. Wallet: signerer proof-JWT
  12. Wallet til Credential Issuer: POST /credential: access token, beviser
  13. Credential Issuer til Wallet: credentials, notification_id
  14. Wallet: validerer og gemmer
  15. Wallet til Credential Issuer: POST /notify: credential_accepted
  16. Credential Issuer til Wallet: 204 No Content

Når legitimationen ikke er klar endnu: udskudt udstedelse

Eksemplet ovenfor forudsætter, at synsresultatet allerede er endeligt. Hvis synsvirksomheden i stedet skal eskalere et tvivlsomt resultat til en seniorinspektør, kan credential endpoint ikke returnere legitimationen med det samme, så den udskyder udstedelsen.

Øjeblikkelig udstedelse

Credential endpoint returnerer den signerede legitimation i samme svar som anmodningen.

Udskudt udstedelse

Credential endpoint svarer i stedet med HTTP 202, et transaction_id og et interval. Wallet spørger deferred credential endpoint med dette id og venter mindst intervallets antal sekunder mellem hver anmodning, indtil legitimationen er klar.

transaction_idspørger deferred_credential_endpoint

Udskudt svar fra /credential

HTTP/1.1 202 Accepted
Content-Type: application/json

{
  "transaction_id": "fi-tx-55c2",
  "interval": 900
}

Spørger /deferred, indtil den er klar

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.

At lukke løkken: notification endpoint

Efter udstedelsen kan wallet fortælle issueren, hvad der skete med legitimationen, ved hjælp af notification_id fra legitimationssvaret. Wallets er ikke forpligtet til at sende disse notifikationer, og levering er ikke garanteret, så en issuer kan ikke tolke en manglende notifikation som noget som helst.

Wallet

Sender en hændelse til issuerens notification_endpoint for det modtagne notification_id, som dækker alle legitimationer i det svar

credential_accepted

Gemt i wallet

credential_failure

Udstedelsen mislykkedes af en anden grund, for eksempel at legitimationen ikke bestod validering

credential_deleted

Udstedelsen mislykkedes på grund af indehaveren, for eksempel fordi denne afslog at gemme den

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"
}

Relaterede begreber

Ofte stillede spørgsmål

Hvordan ved en wallet, hvilken grant-type der skal bruges?

Credential Offer angiver grant-typen i sit grants-objekt. authorization_code er til stede, når issueren vil have, at indehaveren logger ind som en del af flowet. pre-authorized_code er til stede, når indehaveren allerede var autentificeret på den kanal, hvor tilbuddet blev oprettet, for eksempel af synsinspektøren på synsstedet i det gennemarbejdede eksempel nedenfor. Et tilbud kan angive begge dele, og wallet vælger så en af dem. Hvis tilbuddet slet ikke har et grants-objekt, slår wallet op, hvilke grant-typer authorization server understøtter, i dens metadata.

Hvorfor henter wallet issuerens metadata, før den anmoder om noget som helst?

Credential Offer angiver kun credential_configuration_ids, issuerens URL og grants. Issuerens metadata, som leveres fra en well-known-sti, beskriver hver konfiguration: dens format, dens claims og de prooftyper den accepterer, samt endpoints for nonce, credential, deferred og notification. Den angiver også, hvilken authorization server der skal bruges, og den servers egne metadata giver token endpoint. Uden begge dele ville wallet ikke vide, hvordan den skal bygge gyldige anmodninger, eller hvad den skal vise indehaveren før samtykke.

Hvad beviser bevis på nøglebesiddelse egentlig?

Det beviser, at den wallet, der anmoder om legitimationen, besidder den private nøgle, som legitimationen bliver bundet til, ikke blot at den har et gyldigt access token. Wallet signerer en proof-JWT over issuer-identifikatoren og en frisk c_nonce fra issuerens nonce endpoint med den nøgle. Issueren indlejrer den tilsvarende offentlige nøgle i legitimationen. En verifier, der kræver nøglebinding, beder indehaveren om at signere med samme nøgle igen ved fremvisning, så en kopieret legitimation uden nøglen ikke består den kontrol.

Hvorfor skulle en issuer udskyde udstedelsen i stedet for at returnere legitimationen med det samme?

Nogle kontroller, som issueren udfører, før en legitimation udstedes, kan ikke gennemføres inden for en enkelt HTTP-anmodning, for eksempel en manuel gennemgang eller et opkald til et langsomt eksternt register. Udskudt udstedelse lader credential endpoint svare med det samme med et transaction_id i stedet for at blokere forbindelsen, og wallet spørger det udskudte endpoint med det id, indtil kontrollen er færdig, og legitimationen er klar til afhentning.

Er notification endpoint obligatorisk for en issuer at implementere?

Nej. Det er valgfrit for issuere, og wallets er heller ikke forpligtet til at bruge det. Når begge understøtter det, får issueren at vide, hvad der skete efter udstedelsen: legitimationerne blev gemt (credential_accepted), indehaveren stoppede udstedelsen, for eksempel ved at afslå at gemme dem (credential_deleted), eller den mislykkedes af en anden grund (credential_failure). Levering er ikke garanteret, så en issuer bør behandle en notifikation som nyttig information, aldrig som et pålideligt bevis, og kan ikke tolke noget ud fra en manglende notifikation.

Kilder

  1. OpenID for Verifiable Credential Issuance 1.0
  2. OpenID4VC High Assurance Interoperability Profile 1.0
  3. EUDI Wallet Architecture and Reference Framework

Denne side er udelukkende til orientering og udgør ikke juridisk rådgivning. Kontakt OpenID Foundation og Europa-Kommissionen direkte for autoritativ vejledning.

Tal med os om integration med EUDI Wallet