Wystawianie poświadczeń OpenID4VCI wyjaśnione: od oferty do zaakceptowanego poświadczenia
OpenID4VCI, OpenID for Verifiable Credential Issuance, określa, jak portfel żąda i otrzymuje poświadczenie od wystawcy. Pojedyncze wystawienie przechodzi przez kilka odrębnych kroków, zanim portfel faktycznie uzyska użyteczne poświadczenie, a każdy z nich ma swój własny sposób niepowodzenia. Ta strona krok po kroku omawia cały ten cykl życia, na oryginalnych przykładach praktycznych.
Dwa sposoby na rozpoczęcie: kod wstępnie autoryzowany i kod autoryzacyjny
Wystawienie często zaczyna się od Credential Offer wysłanej przez wystawcę, a wskazany w niej grant decyduje, jak portfel zostanie autoryzowany. Portfel może też sam rozpocząć wystawienie, bez żadnej oferty, korzystając z przepływu kodu autoryzacyjnego. Oba przepływy kończą się w tym samym miejscu: portfel posiada token dostępu, którego może użyć, aby zażądać poświadczenia.
Przepływ z kodem wstępnie autoryzowanym
1. Wystawca
Zna już posiadacza, wydaje Credential Offer z parametrem pre-authorized_code
2. Portfel
Wymienia kod w punkcie końcowym tokenu, opcjonalnie z kodem transakcji
3. Portfel
Żąda poświadczenia z dowodem posiadania swojego klucza
Przepływ z kodem autoryzacyjnym
1. Portfel
Skanuje Credential Offer wskazującą grant authorization_code lub sam rozpoczyna przepływ bez oferty
2. Serwer autoryzacji
Prowadzi posiadacza przez logowanie i wyrażenie zgody, a następnie wydaje kod
3. Portfel
Wymienia kod na token, a następnie żąda poświadczenia
Pełny cykl życia oferty poświadczenia
Specyfikacja nie definiuje nazwanych stanów, ale przepływ zainicjowany przez wystawcę, w którym poświadczenie jest wystawiane od razu, najłatwiej śledzić jako sekwencję poniżej. Każdy krok może zawieść na swój sposób, a implementacja portfela musi obsłużyć te ścieżki, a nie tylko tę pomyślną.
Przykład praktyczny: świadectwo sprawności technicznej dla floty transportowej
Stacja kontroli pojazdów wystawia świadectwo sprawności technicznej do portfela biznesowego firmy transportowej po rutynowej kontroli. Inspektor uwierzytelnił już kierownika floty w punkcie kontroli, więc wystawca korzysta z przepływu kodu wstępnie autoryzowanego. Poniższe kroki śledzą to pojedyncze wystawienie, od oferty do zaakceptowanego poświadczenia.
Przykład przedstawia podstawową wersję OpenID4VCI. Wdrożenia o wysokim poziomie zaufania, takie jak EUDI Wallet, stosują na jej bazie profil HAIP, który dodaje tokeny dostępu powiązane z DPoP, atestację portfela w punkcie końcowym tokenu oraz atestację kluczy dla kluczy poświadczeń. Pominięto je tutaj, aby każdy krok pozostał czytelny.
1. Oferta poświadczenia
Terminal stacji kontroli pojazdów wyświetla kod QR. Zawiera on adres URI zaczynający się od openid-credential-offer://, który przenosi poniższą ofertę zakodowaną w formacie URL w parametrze credential_offer, albo credential_offer_uri, z którego portfel ją pobiera. Portfel skanuje kod i odczytuje, jakie poświadczenie jest oferowane oraz jak je odebrać.
Wystawca
Wyświetla kod QR z Credential Offer
Wskazuje konfigurację poświadczenia oraz 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. Odkrycie metadanych wystawcy
Zanim o cokolwiek poprosi, portfel pobiera metadane wystawcy, aby dowiedzieć się, co zawiera roadworthiness_certificate oraz jakie punkty końcowe wywoływać. Metadane nie wymieniają osobnych serwerów autoryzacji, więc wystawca jest jednocześnie swoim własnym serwerem autoryzacji, a portfel odczytuje punkt końcowy tokenu z metadanych tego serwera.
Portfel
GET /.well-known/openid-credential-issuer
Poznaje format poświadczenia, atrybuty i akceptowane typy dowodów, a także punkty końcowe nonce, poświadczeń, odroczeń i powiadomień
Metadane wystawcy poświadczeń (fragment)
{
"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"] }
]
}
}
}
}Metadane serwera autoryzacji (fragment), z /.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. Żądanie tokenu
Portfel wymienia kod wstępnie autoryzowany w punkcie końcowym tokenu wraz z kodem transakcji, który stacja kontroli wysłała na telefon kierownika floty. Wysłanie tego kodu drugim kanałem oznacza, że osoba, która sfotografuje kod QR zza ramienia, nadal nie może go wykorzystać.
Portfel
POST /token
Wysyła pre-authorized_code i tx_code, otrzymuje token dostępu ograniczony do tej oferty
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
Odpowiedź
{
"access_token": "fi-at-3d91e0",
"token_type": "Bearer",
"expires_in": 86400
}4. Dowód posiadania
Ponieważ metadane wymieniają nonce_endpoint, portfel najpierw pobiera stamtąd świeży c_nonce. Następnie udowadnia, że posiada klucz prywatny, z którym poświadczenie zostanie powiązane, podpisując proof JWT nad identyfikatorem wystawcy i tym c_nonce.
Portfel
POST /nonce, a następnie podpisuje proof JWT kluczem, z którym poświadczenie zostanie powiązane
Wiąże poświadczenie z tym kluczem, a nie jedynie z tym, kto posiada token dostępu
POST /nonce HTTP/1.1 Host: issuer.fleetinspect.example
Odpowiedź
{
"c_nonce": "fi-nonce-77aa"
}Portfel buduje teraz proof JWT z dwóch obiektów JSON, nagłówka i ładunku, i podpisuje je kluczem prywatnym, z którym poświadczenie zostanie powiązane.
Nagłówek: czym jest ten JWT i jaki klucz go podpisał
{
"typ": "openid4vci-proof+jwt",
"alg": "ES256",
"jwk": { "kty": "EC", "crv": "P-256", "x": "...", "y": "..." }
}typ: oznacza to jako dowód klucza OpenID4VCI, więc nie można go pomylić z żadnym innym rodzajem JWTalg: algorytm podpisu, jeden z tych, które wystawca wymienił w proof_signing_alg_values_supportedjwk: klucz publiczny, z którym poświadczenie zostanie powiązane; wystawca sprawdza podpis względem niego
Ładunek: dla kogo jest dowód i kiedy powstał
{
"aud": "https://issuer.fleetinspect.example",
"iat": 1789376400,
"nonce": "fi-nonce-77aa"
}aud: identyfikator wystawcy, dzięki czemu dowodu nie można odtworzyć u innego wystawcyiat: czas utworzenia dowodu, w sekundach od 1970 rokunonce: c_nonce z punktu końcowego nonce, który pokazuje, że dowód jest świeży
Wynik podpisany
Nagłówek i ładunek są każdy kodowane w base64url i łączone kropką. Portfel podpisuje ten ciąg swoim kluczem prywatnym i dołącza zakodowany w base64url podpis po drugiej kropce. Powstały ciąg to proof JWT, który portfel wysyła w żądaniu poświadczenia w kroku 5.
base64url(header) . base64url(payload) . base64url(signature) eyJ0eXAiOiJvcGVuaWQ0dmNpLXByb29mK2p3dCIs... .eyJhdWQiOiJodHRwczovL2lzc3Vlci5mbGVldGluc3BlY3QuZXhhbXBsZSIs... .<ES256 signature>
5. Żądanie poświadczenia
Portfel wywołuje punkt końcowy poświadczeń z tokenem dostępu i dowodem, a wystawca generuje i zwraca podpisane poświadczenie.
Portfel
POST /credential
Wysyła token dostępu, identyfikator konfiguracji i proof JWT, otrzymuje podpisane poświadczenie oraz 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>"]
}
}Odpowiedź
{
"credentials": [
{ "credential": "<issuer-signed SD-JWT VC>" }
],
"notification_id": "fi-notif-9012"
}Cały przepływ w skrócie
Ten diagram sekwencji łączy pięć kroków przykładu praktycznego w całość, od oferty po powiadomienie. Strzałki ciągłe to żądania, przerywane to odpowiedzi, a kropkowana strzałka to kod transakcji podróżujący poza protokołem w wiadomości SMS.
Portfel
Portfel biznesowy firmy transportowej
Serwer autoryzacji
W tym przykładzie prowadzony przez samego wystawcę
Wystawca poświadczeń
Stacja kontroli pojazdów
- Wystawca poświadczeń do Portfel: Credential Offer, przedstawiona jako kod QR
- Wystawca poświadczeń do Portfel: tx_code, wysłany SMS-em na telefon kierownika floty
- Portfel do Wystawca poświadczeń: GET /.well-known/openid-credential-issuer
- Wystawca poświadczeń do Portfel: metadane wystawcy poświadczeń
- Portfel do Serwer autoryzacji: GET /.well-known/oauth-authorization-server
- Serwer autoryzacji do Portfel: metadane serwera autoryzacji
- Portfel do Serwer autoryzacji: POST /token: pre-authorized_code, tx_code
- Serwer autoryzacji do Portfel: access_token
- Portfel do Wystawca poświadczeń: POST /nonce
- Wystawca poświadczeń do Portfel: c_nonce
- Portfel: podpisuje proof JWT
- Portfel do Wystawca poświadczeń: POST /credential: token dostępu, dowody
- Wystawca poświadczeń do Portfel: credentials, notification_id
- Portfel: waliduje i zapisuje
- Portfel do Wystawca poświadczeń: POST /notify: credential_accepted
- Wystawca poświadczeń do Portfel: 204 No Content
Gdy poświadczenie nie jest jeszcze gotowe: wystawienie odroczone
Powyższy przykład zakłada, że wynik kontroli jest już ostateczny. Jeśli stacja kontroli musi zamiast tego przekazać wynik graniczny starszemu inspektorowi, punkt końcowy poświadczeń nie może od razu zwrócić poświadczenia, więc odracza wystawienie.
Natychmiastowe wystawienie
Punkt końcowy poświadczeń zwraca podpisane poświadczenie w tej samej odpowiedzi co żądanie.
Odroczone wystawienie
Punkt końcowy poświadczeń odpowiada kodem HTTP 202, wartością transaction_id oraz interwałem zamiast poświadczenia. Portfel odpytuje deferred_credential_endpoint tym identyfikatorem, czekając co najmniej tyle sekund między żądaniami, ile wskazuje interwał, aż poświadczenie będzie gotowe.
Odroczona odpowiedź z /credential
HTTP/1.1 202 Accepted
Content-Type: application/json
{
"transaction_id": "fi-tx-55c2",
"interval": 900
}Odpytywanie /deferred, aż będzie gotowe
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.Zamykanie pętli: punkt końcowy powiadomień
Po wystawieniu portfel może poinformować wystawcę, co stało się z poświadczeniem, używając notification_id z odpowiedzi poświadczenia. Portfele nie są zobowiązane do wysyłania tych powiadomień, a dostarczenie nie jest gwarantowane, więc wystawca nie może interpretować braku powiadomienia w żaden sposób.
Portfel
Wysyła zdarzenie do notification_endpoint wystawcy dla otrzymanego notification_id, które obejmuje każde poświadczenie z tej odpowiedzi
Zapisane w portfelu
Wystawienie nie powiodło się z innego powodu, na przykład poświadczenie nie przeszło walidacji
Wystawienie nie powiodło się z powodu posiadacza, na przykład odmówił on zapisania poświadczenia
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"
}Powiązane pojęcia
Najczęściej zadawane pytania
Skąd portfel wie, jakiego typu grantu użyć?
Credential Offer wskazuje grant w swoim obiekcie grants. authorization_code występuje, gdy wystawca chce, aby posiadacz zalogował się w ramach przepływu. pre-authorized_code występuje, gdy posiadacz był już uwierzytelniony na kanale, na którym utworzono ofertę, na przykład przez inspektora w punkcie kontroli w poniższym przykładzie praktycznym. Oferta może wymieniać oba, a portfel wybiera wtedy jeden z nich. Jeśli oferta w ogóle nie ma obiektu grants, portfel sprawdza w metadanych, jakie typy grantów obsługuje serwer autoryzacji.
Dlaczego portfel pobiera metadane wystawcy, zanim o cokolwiek poprosi?
Credential Offer wskazuje jedynie credential_configuration_ids, adres URL wystawcy oraz grants. To metadane wystawcy, udostępniane pod dobrze znaną ścieżką, opisują każdą konfigurację: jej format, atrybuty i akceptowane typy dowodów, a także punkty końcowe nonce, poświadczeń, odroczeń i powiadomień. Metadane wskazują też, z jakiego serwera autoryzacji korzystać, a metadane tego serwera podają punkt końcowy tokenu. Bez obu tych źródeł portfel nie wiedziałby, jak zbudować poprawne żądania ani co pokazać posiadaczowi przed wyrażeniem zgody.
Co właściwie udowadnia dowód posiadania?
Udowadnia, że portfel żądający poświadczenia posiada klucz prywatny, z którym poświadczenie zostanie powiązane, a nie jedynie to, że ma ważny token dostępu. Portfel podpisuje proof JWT nad identyfikatorem wystawcy i świeżym c_nonce z punktu końcowego nonce wystawcy, używając tego klucza. Wystawca osadza odpowiadający mu klucz publiczny w poświadczeniu. Weryfikator wymagający powiązania z kluczem prosi posiadacza o ponowne podpisanie tym samym kluczem podczas prezentacji, więc skopiowane poświadczenie bez klucza nie przejdzie tej kontroli.
Dlaczego wystawca miałby odroczyć wystawienie zamiast od razu zwrócić poświadczenie?
Niektóre kontrole, które wystawca wykonuje przed wygenerowaniem poświadczenia, nie mogą zakończyć się w ramach jednego żądania HTTP, na przykład ręczna weryfikacja lub zapytanie do wolnego, zewnętrznego rejestru. Odroczone wystawienie pozwala punktowi końcowemu poświadczeń odpowiedzieć od razu wartością transaction_id zamiast blokować połączenie, a portfel odpytuje punkt końcowy odroczeń tym identyfikatorem, aż kontrola się zakończy i poświadczenie będzie gotowe do odebrania.
Czy punkt końcowy powiadomień jest obowiązkowy do zaimplementowania przez wystawcę?
Nie. Jest opcjonalny dla wystawców, a portfele również nie muszą go używać. Gdy obie strony go obsługują, wystawca dowiaduje się, co stało się po wystawieniu: poświadczenia zostały zapisane (credential_accepted), posiadacz przerwał wystawienie, na przykład odmawiając zapisania poświadczeń (credential_deleted), albo wystąpił inny błąd (credential_failure). Dostarczenie nie jest gwarantowane, więc wystawca powinien traktować powiadomienie jako przydatną informację, nigdy jako wiarygodny zapis, i nie może wyciągać żadnych wniosków z jego braku.
Źródła
Ta strona ma charakter informacyjny i nie stanowi porady prawnej. Aby uzyskać wiążące wskazówki, skontaktuj się bezpośrednio z OpenID Foundation oraz Komisją Europejską.