OpenID4VCI-utfärdande av legitimation förklarat: från erbjudande till accepterad legitimation
OpenID4VCI, OpenID for Verifiable Credential Issuance, definierar hur en wallet begär och tar emot en legitimation från en issuer. Ett enskilt utfärdande går igenom flera distinkta steg innan wallet faktiskt har en användbar legitimation, och varje steg har sitt eget sätt att misslyckas. Den här sidan går igenom hela livscykeln i detalj, med egna genomarbetade exempel.
Två sätt att börja: förhandsauktoriserad kod och auktoriseringskod
Utfärdande startar ofta från en Credential Offer som skickas av issuern, och grant-typen den anger avgör hur wallet auktoriseras. En wallet kan också starta utfärdandet själv, utan något erbjudande, med hjälp av auktoriseringskodflödet. Båda flödena slutar på samma ställe: wallet innehar ett access token den kan använda för att begära legitimationen.
Flöde med förhandsauktoriserad kod
1. Issuer
Känner redan innehavaren och lämnar ut en Credential Offer med en pre-authorized_code
2. Wallet
Löser in koden hos token endpoint, eventuellt tillsammans med en transaktionskod
3. Wallet
Begär legitimationen med ett bevis på att den innehar nyckeln
Flöde med auktoriseringskod
1. Wallet
Skannar en Credential Offer som anger ett authorization_code-grant, eller startar flödet själv utan något erbjudande
2. Authorization Server
Leder innehavaren genom inloggning och samtycke, och utfärdar sedan en kod
3. Wallet
Växlar koden mot ett token och begär sedan legitimationen
Hela livscykeln för ett legitimationserbjudande
Specifikationen definierar inga namngivna tillstånd, men ett issuer-initierat flöde där legitimationen utfärdas direkt är enklast att följa, som i sekvensen nedan. Varje steg kan misslyckas på sitt eget sätt, och en wallet-implementation måste hantera de vägarna, inte bara den lyckade.
Exempel: ett besiktningsintyg för en åkerifordonsflotta
Ett fordonsbesiktningsorgan utfärdar ett besiktningsintyg till ett åkeris business wallet efter en rutinbesiktning. Inspektören har redan autentiserat flottchefen på besiktningsstationen, så issuern använder flödet med förhandsauktoriserad kod. Stegen nedan följer det här enskilda utfärdandet från erbjudande till accepterad legitimation.
Exemplet visar grundläggande OpenID4VCI. Lösningar med hög tillitsnivå, som EUDI Wallet, följer HAIP-profilen ovanpå detta, vilket lägger till DPoP-bundna access tokens, wallet attestation hos token endpoint och key attestation för legitimationens nycklar. Detta utelämnas här för att hålla varje steg lättläst.
1. Legitimationserbjudande
Besiktningsorganets terminal visar en QR-kod. Den innehåller en URI som börjar med openid-credential-offer:// och som bär erbjudandet nedan, URL-kodat i en credential_offer-parameter, eller en credential_offer_uri som wallet hämtar det från. Wallet skannar koden och läser vilken legitimation som erbjuds och hur den hämtas.
Issuer
Visar en QR-kod med en Credential Offer
Anger legitimationskonfigurationen och ett 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. Upptäckt av issuerns metadata
Innan wallet begär något alls hämtar den issuerns metadata för att ta reda på vad roadworthiness_certificate innehåller och vilka endpoints som ska anropas. Metadatan listar inga separata authorization servers, så issuern är sin egen authorization server, och wallet läser token endpoint från den serverns metadata.
Wallet
GET /.well-known/openid-credential-issuer
Får reda på legitimationsformatet, claims och accepterade bevistyper, samt endpoints för nonce, credential, deferred och notification
Metadata för credential issuer (utdrag)
{
"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 för authorization server (utdrag), från /.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-begäran
Wallet löser in den förhandsauktoriserade koden hos token endpoint, tillsammans med transaktionskoden som besiktningsorganet skickade till flottchefens telefon. Att skicka koden via en andra kanal gör att den som fotograferar QR-koden över axeln ändå inte kan lösa in den.
Wallet
POST /token
Skickar pre-authorized_code och tx_code, tar emot ett access token begränsat till detta erbjudande
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å nyckelinnehav
Eftersom metadatan listar en nonce_endpoint hämtar wallet först en färsk c_nonce därifrån. Den bevisar sedan att den innehar den privata nyckel som legitimationen ska bindas till, genom att signera en proof-JWT över issuer-identifieraren och den c_nonce:n.
Wallet
POST /nonce, signerar sedan en proof-JWT med den nyckel legitimationen ska bindas till
Binder legitimationen till den nyckeln, inte bara till den som innehar access token
POST /nonce HTTP/1.1 Host: issuer.fleetinspect.example
Svar
{
"c_nonce": "fi-nonce-77aa"
}Wallet bygger nu proof-JWT:n av två JSON-objekt, en header och en payload, och signerar dem med den privata nyckel legitimationen ska bindas till.
Header: vad den här JWT:n är och vilken nyckel som signerade den
{
"typ": "openid4vci-proof+jwt",
"alg": "ES256",
"jwk": { "kty": "EC", "crv": "P-256", "x": "...", "y": "..." }
}typ: markerar detta som en OpenID4VCI key proof, så att den inte kan misstas för någon annan typ av JWTalg: signeringsalgoritmen, en av dem issuern listade i proof_signing_alg_values_supportedjwk: den publika nyckel legitimationen ska bindas till; issuern kontrollerar signaturen mot den
Payload: vem beviset gäller och när det skapades
{
"aud": "https://issuer.fleetinspect.example",
"iat": 1789376400,
"nonce": "fi-nonce-77aa"
}aud: issuer-identifieraren, så att beviset inte kan återanvändas hos en annan issueriat: tidpunkten då beviset skapades, i sekunder sedan 1970nonce: c_nonce från nonce endpoint, vilket visar att beviset är färskt
Signerat resultat
Header och payload base64url-kodas var för sig och länkas samman med en punkt. Wallet signerar den strängen med sin privata nyckel och lägger till den base64url-kodade signaturen efter en andra punkt. Den resulterande strängen är den proof-JWT som wallet skickar i legitimationsbegäran i steg 5.
base64url(header) . base64url(payload) . base64url(signature) eyJ0eXAiOiJvcGVuaWQ0dmNpLXByb29mK2p3dCIs... .eyJhdWQiOiJodHRwczovL2lzc3Vlci5mbGVldGluc3BlY3QuZXhhbXBsZSIs... .<ES256 signature>
5. Legitimationsbegäran
Wallet anropar credential endpoint med access token och beviset, och issuern utfärdar och returnerar den signerade legitimationen.
Wallet
POST /credential
Skickar access token, konfigurations-id och proof-JWT, tar emot den signerade legitimationen och en 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"
}Hela flödet i ett svep
Det här sekvensdiagrammet sätter ihop de fem stegen i exemplet, från erbjudandet till notifieringen. Heldragna pilar är begäranden, streckade pilar är svar, och den prickade pilen är transaktionskoden som färdas utanför protokollet via sms.
Wallet
Åkeriets business wallet
Authorization Server
Drivs av issuern själv i det här exemplet
Credential Issuer
Fordonsbesiktningsorgan
- Credential Issuer till Wallet: Credential Offer, visad som QR-kod
- Credential Issuer till Wallet: tx_code, skickad till flottchefens telefon via sms
- Wallet till Credential Issuer: GET /.well-known/openid-credential-issuer
- Credential Issuer till Wallet: metadata för credential issuer
- Wallet till Authorization Server: GET /.well-known/oauth-authorization-server
- Authorization Server till Wallet: metadata för authorization server
- Wallet till Authorization Server: POST /token: pre-authorized_code, tx_code
- Authorization Server till Wallet: access_token
- Wallet till Credential Issuer: POST /nonce
- Credential Issuer till Wallet: c_nonce
- Wallet: signerar proof-JWT
- Wallet till Credential Issuer: POST /credential: access token, bevis
- Credential Issuer till Wallet: credentials, notification_id
- Wallet: validerar och lagrar
- Wallet till Credential Issuer: POST /notify: credential_accepted
- Credential Issuer till Wallet: 204 No Content
När legitimationen inte är klar än: uppskjutet utfärdande
Exemplet ovan förutsätter att besiktningsresultatet redan är slutgiltigt. Om besiktningsorganet i stället behöver eskalera ett tveksamt resultat till en senior inspektör kan credential endpoint inte returnera legitimationen direkt, och den skjuter då upp utfärdandet.
Omedelbar utfärdande
Credential endpoint returnerar den signerade legitimationen i samma svar som begäran.
Uppskjutet utfärdande
Credential endpoint svarar i stället med HTTP 202, en transaction_id och ett intervall. Wallet frågar deferred credential endpoint med detta id, och väntar minst intervallets antal sekunder mellan varje begäran, tills legitimationen är klar.
Uppskjutet svar från /credential
HTTP/1.1 202 Accepted
Content-Type: application/json
{
"transaction_id": "fi-tx-55c2",
"interval": 900
}Frågar /deferred tills den är 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.Att sluta cirkeln: notification endpoint
Efter utfärdandet kan wallet berätta för issuern vad som hände med legitimationen, med hjälp av notification_id från legitimationssvaret. Wallets är inte skyldiga att skicka dessa notifieringar och leverans är inte garanterad, så en issuer kan inte tolka en utebliven notifiering som att den betyder något alls.
Wallet
Skickar en händelse till issuerns notification_endpoint för den mottagna notification_id, vilken omfattar alla legitimationer i det svaret
Lagrad i wallet
Utfärdandet misslyckades av någon annan anledning, till exempel att legitimationen inte validerades
Utfärdandet misslyckades på grund av innehavaren, till exempel att denne avböjde att lagra 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"
}Relaterade begrepp
Vanliga frågor
Hur vet en wallet vilken grant-typ som ska användas?
Credential Offer anger grant-typen i sitt grants-objekt. authorization_code förekommer när issuern vill att innehavaren ska logga in som en del av flödet. pre-authorized_code förekommer när innehavaren redan var autentiserad på den kanal där erbjudandet skapades, till exempel av inspektören på besiktningsstationen i exemplet nedan. Ett erbjudande kan lista båda, och wallet väljer då en av dem. Om erbjudandet helt saknar ett grants-objekt slår wallet i stället upp vilka grant-typer authorization server stöder i sin metadata.
Varför hämtar wallet issuerns metadata innan den begär något alls?
Credential Offer anger endast credential_configuration_ids, issuerns URL och grants. Issuerns metadata, som tillhandahålls från en well-known-sökväg, beskriver varje konfiguration: dess format, dess claims och vilka bevistyper den accepterar, samt endpoints för nonce, credential, deferred och notification. Den anger också vilken authorization server som ska användas, och den serverns egen metadata ger token endpoint. Utan båda delarna skulle wallet inte veta hur den ska bygga giltiga begäranden eller vad den ska visa innehavaren innan samtycke ges.
Vad bevisar egentligen beviset på nyckelinnehav?
Det bevisar att den wallet som begär legitimationen innehar den privata nyckel som legitimationen ska bindas till, inte bara att den har ett giltigt access token. Wallet signerar en proof-JWT över issuer-identifieraren och en färsk c_nonce från issuerns nonce endpoint, med den nyckeln. Issuern lägger in den motsvarande publika nyckeln i legitimationen. En verifier som kräver nyckelbindning ber innehavaren signera med samma nyckel igen vid uppvisande, så en kopierad legitimation utan nyckeln klarar inte den kontrollen.
Varför skulle en issuer skjuta upp utfärdandet i stället för att returnera legitimationen direkt?
Vissa kontroller som issuern gör innan en legitimation utfärdas kan inte slutföras inom en enda HTTP-begäran, till exempel en manuell granskning eller ett anrop till ett långsamt externt register. Uppskjutet utfärdande låter credential endpoint svara omedelbart med en transaction_id i stället för att blockera anslutningen, och wallet frågar deferred endpoint med det id:t tills kontrollen är klar och legitimationen är redo att hämtas.
Är notification endpoint obligatorisk för en issuer att implementera?
Nej. Det är valfritt för issuers, och wallets är inte heller skyldiga att använda det. När båda stöder det får issuern veta vad som hände efter utfärdandet: legitimationerna lagrades (credential_accepted), innehavaren avbröt utfärdandet, till exempel genom att avböja att lagra dem (credential_deleted), eller det misslyckades av någon annan anledning (credential_failure). Leverans är inte garanterad, så en issuer bör betrakta en notifiering som användbar information, aldrig som ett tillförlitligt register, och kan inte läsa in något i en utebliven notifiering.
Källor
Den här sidan är endast informativ och utgör inte juridisk rådgivning. För auktoritativ vägledning, kontakta OpenID Foundation och Europeiska kommissionen direkt.