OID4VCI: Unique ID for each element in credentials
Abstract
- Each element in the
credentials
array in a credential offer or in anauthorization_details
element whosetype
isopenid_credential
should have a unique identifier. - A credential request should use the identifier.
This makes a credential request simpler and logically prevents the wallet from requesting more than what the end-user has approved. This proposal contains breaking changes.
1. Proposed Changes
1.1. Credential Offer / authorization_details
element of type openid_credential
Current:
{
"credential_issuer": "https://credential-issuer.example.com",
"credentials": [
"UniversityDegree_JWT",
{
"format": "mso_mdoc",
"doctype": "org.iso.18013.5.1.mDL"
},
{
"format": "jwt_vc_json",
"credential_definition": {
"type": [
"VerifiableCredential",
"UniversityDegreeCredential"
]
}
}
]
}
Proposed Change: Insert "id"
into each JSON object. (Note that “string” elements already represent IDs in credentials_supported
.)
{
"credential_issuer": "https://credential-issuer.example.com",
"credentials": [
"UniversityDegree_JWT",
{
"id": "a_unique_identifier_1",
"format": "mso_mdoc",
"doctype": "org.iso.18013.5.1.mDL"
},
{
"id": "a_unique_identifier_2",
"format": "jwt_vc_json",
"credential_definition": {
"type": [
"VerifiableCredential",
"UniversityDegreeCredential"
]
}
}
]
}
1.2. Credential Request
Current:
{
"format": "jwt_vc_json",
"type": [
"VerifiableCredential",
"UniversityDegreeCredential"
],
"proof": {
"proof_type": "jwt",
"jwt": "eyJ..."
}
}
Proposed Change: Insert "id"
and remove properties that were in the credential offer or the authorization_details
element.
{
"id": "a_unique_identifier_2",
"proof": {
"proof_type": "jwt",
"jwt": "eyJ..."
}
}
1.3. Credential Response
Current:
{
"format": "jwt_vc_json",
"credential": "...",
"c_nonce": "...",
"c_nonce_expires_in": 86400
}
Proposed Change: Insert "id"
and remove properties that were in the credential offer or the authorization_details
element.
{
"id": "a_unique_identifier_2",
"credential": "...",
"c_nonce": "...",
"c_nonce_expires_in": 86400
}
1.4. Batch Credential Request
Current:
{
"credential_requests": [
{
"format": "jwt_vc_json",
"type": [
"VerifiableCredential",
"UniversityDegreeCredential"
],
"proof": {
"proof_type": "jwt",
"jwt": "eyJ..."
}
},
{
"format": "mso_mdoc",
"doctype": "org.iso.18013.5.1.mDL",
"proof": {
"proof_type": "jwt",
"jwt": "eyJ..."
}
}
]
}
Proposed Change: Insert "id"
into each JSON object in the credential_requests
array and remove properties that were in the credential offer or the authorization_details
element.
{
"credential_requests": [
{
"id": "a_unique_identifier_2",
"proof": {
"proof_type": "jwt",
"jwt": "eyJ..."
}
},
{
"id": "a_unique_identifier_1",
"proof": {
"proof_type": "jwt",
"jwt": "eyJ..."
}
}
]
}
1.5. Batch Credential Response
Current:
{
"credential_responses": [
{
"format": "jwt_vc_json",
"credential": "..."
},
{
"format": "mso_mdoc",
"credential": "..."
}
],
"c_nonce": "...",
"c_nonce_expires_in": 86400
}
Proposed Change: Insert "id"
into each JSON object in the credential_responses
array and remove properties that were in the credential offer or the authorization_details
element. Note that inserting "id"
can make the order of array elements less important.
{
"credential_responses": [
{
"id": "a_unique_identifier_2",
"credential": "..."
},
{
"id": "a_unique_identifier_1",
"credential": "..."
}
],
"c_nonce": "...",
"c_nonce_expires_in": 86400
}
2. Reason to Propose
The following is an example of an authorization_details
element in Appendix E.
[
{
"type": "openid_credential",
"format": "jwt_vc_json",
"credential_definition": {
"type": [
"VerifiableCredential",
"UniversityDegreeCredential"
],
"credentialSubject": {
"given_name": {},
"last_name": {},
"degree": {}
}
}
}
]
This example implies that the wallet requests three claims, namely, given_name
, last_name
and degree
, only. For data minimization policy (likely to be imposed by regulators), the credential issuer should not issue a Verifiable Credential that includes other claims. At least, an implementer of “OpenID Connect for Identity Assurance 1.0” (me) thinks so.
A cumbersome and error-prone point for implementers is a case where a credential request demands more than the credential offer or the authorization_details
element originally intends to offer. Suppose the following credential request comes.
{
"format": "jwt_jv_json",
"credential_definition": {
"type": [
"VerifiableCredential",
"UniversityDegreeCredential"
],
"credentialSubject": {
"given_name": {},
"birthdate": {},
"degree": {}
}
}
}
This credential request requests the birthdate
claim that is not in the original authorization_details
element. For data minimization policy, this credential request should be rejected or the birthdate
claim should be absent from the Verifiable Credential being issued.
It will be a cumbersome and error-prone task for implementers to write programs to make sure that a credential request is requesting claims within the approved range and it is not requesting more than approved.
Describing almost the same thing in the credential offer / the authorization_details
element and in the credential request is redundant. Considering that validation of their complex formats is not so easy, it is unlikely that majority of implementers would support that redundancy.
Inserting an unique identifier into each elements in the credentials
array and having the wallet use the identifier when making a credential request would be able to make the OID4VCI specification and its implementations simpler, and as a result, the specification would be more acceptable to implementers and the market.
Comments (23)
-
-
reporter My answer to your question is “yes”.
The deeper data structure becomes, the more complex comparison becomes.
For example, if the following two come as an
authorization_details
element and a credential request,authorization_details
:[ { "type": "openid_credential", "format": "jwt_vc_json", "credential_definition": { "type": [ "VerifiableCredential", "SpecificCredential" ], "credentialSubject" { "address": { "country": {}, "postal_code": {}, "region": {} } } } } ]
credential request:
{ "format": "jwt_vc_json", "credential_definition": { "type": [ "VerifiableCredential", "SpecificCredential" ], "credentialSubject": { "address": { "country": {}, "region": {}, "locality": {} } } } }
the credential request should be rejected because
address/locality
is not included in theauthorization_details
element. For the validation, implementations have to compare the nested structures. This validation would become hard when complex structures likeverified_claims
(“OpenID Connect for Identity Assurance 1.0”) are used.Also, it’s not so clear what should happen when a credential request like below comes.
{ "format": "jwt_vc_json", "credential_definition": { "type": [ "VerifiableCredential", "SpecificCredential" ], "credentialSubject": { "address": {} } } }
And, if data structures include requirements and conditions like
claims
(OIDC Core) andverified_claims
(OIDC4IDA), a simple comparison will not work. For example, the followingverified_claims
request{ "verified_claims": { "verification": { "trust_framework": null, "evidence": [ { "type": { "value": "document" }, "method": null, "document_details": { "type": null, "document_number": null } }, { "type": { "value": "electronic_signature" }, "signature_type": null, "issuer": null, "serial_number": null } ] }, "claims": { "given_name": null } } }
completely subsumes the following
verified_claims
request.{ "verified_claims": { "verification": { "trust_framework": null, "evidence": [ { "type": { "value": "document" }, "method": null, "document_details": { "type": null, "document_number": null } } ] }, "claims": { "given_name": null } } }
However, adopting the latter breaks the Logical-OR rule on
evidence
and may result in different output.Another example. This request
{ "credentialSubject": { "favorites": { "values": ["apple", "banana"] } } }
seemingly subsumes the following.
{ "credentialSubject": { "favorites": {} } }
However, the meaning is opposite. The condition of the former is more strict than that of the latter.
If it is ensured that complex things like
verified_claims
never appear in a credential offer / anauthorization_details
and a credential request, the current specification won’t be a big hurdle. However, still my proposal will bring benefits.As you may notice, I’m thinking how to combine OID4VCI and OIDC4IDA for GAIN (Global Assured Identity Network). My proposal has come from the context.
-
Thanks for the explanation.
As far as I understand you want the wallet to always request a credential using the identifier, which means we would remove the format identifier and all content definition from the credential request?
The effect of your proposal is that the wallet would always request exactly the payload the user has consented for, no subset. Right?
I’m not sure what that means regarding subsequent attempts to obtain a credential or to obtain multiple instances of the same credential with the same token.
Subsequent requests: the wallet wants to refresh a credential because it has a short expiration. Since the wallet has a refresh token, it can obtain a fresh access token and get a new credential. Would the AS create a new identifier? Would the wallet use the pre-existing identifier?
multiple instances: the user consents to issue (a) mobile drivers license, however since the deployments requires use of ephemeral credentials (to prevent co-relation across Verifiers), the wallet obtains a batch of five credentials, each of it bound to a different key for cryptographic holder binding. Would that mean the same identifier can be used multiple times?
Note: issue
#1923asks for a similar extension for scenarios where the issuer wants to issue multiple credentials of the same type. -
- changed status to open
05182023 SIOP call: Taka is trying to minimize the errors that could come from the range of credential formats available. big breaking change, some outstanding questions, but worth pursuing :)
-
I think one use-case we will not be able to support with this change is if the user gave consent for the issuance of the “given_name", "last_name", "degree", and “birthdate” at the authz endpoint and wallet wants to receive multiple credentials - one that contains only birthdate and the other that contains other three claims. I don’t know if this is a niche scenario or if it is valid or not, but thought I’d point it out.
-
while the access token is valid, the wallet wants to refresh a credential because it has a short expiration. Since the wallet has a refresh token, it can obtain a fresh access token and get a new credential. Would the AS create a new identifier? Would the wallet use the pre-existing identifier?
How would the wallet get a new identifier? when receiving a fresh access token? I think same identifier would work, but I am not sure how this ties into the scenario that we have been discussing with @Tobias Looker in another issue, where the claim values in the fresh credential have changed and the issuer want to communicate that to the wallet - guess the credential response will contain a new identifier…
-
- I agree that comparing the
credentialSubject
that was authorized (i.e. thecredentialSubject
in theauthorization_details
parameters) with thecredentialSubject
on the credential request may become quite complex, namely with nested structures. - IINM, a similar problem already exists when using a
authorization_details
when refreshing an access token - how to ensure thecredentialSubject
in the refresh request’sauthorization_details
is a subset of thecredentialSubject
in the authorization request'sauthorization_details
. -
When using a credential offer, the ID would be defined by the credential issuer, however when directly using an authorization request with an
authorization_details
, the IDs would be defined by the wallet, right?- BTW, is it possible to use authorization_details on an authorization request triggered by a credential offer? I.e. use both
issuer_state
andauthorization_details
? If so, we could potentially have IDs controlled by both parties in the same authorization request.
- BTW, is it possible to use authorization_details on an authorization request triggered by a credential offer? I.e. use both
- I agree that comparing the
-
I think the IDs should be defined by the AS and conveyed in the token response in any use case. One might argue that the identifiers might already be known when the credential offer is generated. But there is basically no need for the wallet to know them then as it first needs to request the access token before it can request the credentials. So I would opt for simplification of the flow.
re BTW: of course, esp. in conjunction with the code grant type! It is straight forward to derive the authorization details from the credential types listed in the credential offer.
-
Does this ID need to be unique to the transaction? I don’t think it does, and if so, the suggestion in PR #520 to use
scopes
value as the identifier stands, no? -
-
reporter I’m feeling like retracting this proposal because my proposal makes it impossible for wallets to request a “subset” of claims.
My concern is that it is unclear whether a mechanical comparison between two complex JSON structures can assure that permissions represented by one JSON structure are all covered by the other. Sometimes a smaller strucutre can represent a broader permission (e.g.
{"permissions":["read","write"]}
vs{"permissions":["all"]}
). However, without a well-defined logic in the spec, what implementers can do is a mechanical comparison at best.My proposal can remove the complexity of permission comparison but makes it impossible to request a “subset” of claims, which may be unacceptable in some use cases.
-
One limitation in not having an unique identifier, is that it can actually be ambiguous for the wallet / receiver of the credential which credential it has received.
Taking a similar example from
#1922, where the following credentials are supported.
{ "credential_supported": [ { "format": "jwt_vc_json", "id": "AERO-123-FluidMechanics-II-2023", "types": ["VerifiableCredential", "AerospaceEngineering"], "display": { "name": "Fluid Mechanics", /* ... other display properties ... */ } }, { "format": "jwt_vc_json", "id": "AERO-631-SoldingMechanics-IV-2023", "types": ["VerifiableCredential", "AerospaceEngineering"], "display": { "name": "Solid Mechanics", /* ... other display properties ... */ } } ] }
If a credential offer would then include the following:
{ "credentials": ["AERO-123-FluidMechanics-II-2023", "AERO-631-SoldingMechanics-IV-2023"] }
The request for both would something like:
{ "format": "jwt_vc_json", "types": ["VerifiableCredential", "AerospaceEngineering"] }
It is now (maybe I’m missing something), not 100% clear to the issuer which credential is being requested (
AERO-123-FluidMechanics-II-2023
orAERO-631-SoldingMechanics-IV-2023
?), and it’s also not clear for the wallet which credential is being issued. Both could have different display metadata, and thus it’s not really possible to know for the wallet which credential supported display metadata to associate with the issued credential. -
talked to our engineering team, we are in huge support of having an
id
parameter (or equivalent) in the Credential Request. -
We are pro this change. We primarily use batch issuing with many of the same type (such as badges). Matching based on properties that are not necessarily unique can lead to unexpected behaviour.
- We do not have use cases for users to request a subset of claims
- In our model, subsequent (refreshed) credentials have a new
id
as the credential offer is retrieved again once the person is authenticated
-
@Kristina Yasuda
Hello,
may I ask when are you planning to introduce theid
parameter in the Credential Endpoint on the specification?
We are really interested in adopting theid
parameter to our implementation as we have raised the issue of having more than one credentials available of the same type for a single user.
-
A few clarifying questions…
Would this proposal still allow credentials to be requested using “format” and “type” or only with “id”?
Given we’re credential-format agnostic, are we sure that all credential formats will be able to carry an “id” value?
And while I hate to even ask this, might the use of “id” conflict with JSON-LD’s use of “id”?
-
@Emmanouil Kournianos yes, I just did a PR #612. please review.
-
If the problem raised in this PR is the fact that the subset of claims requested using
credentialSubject
parameter in authorization request and credential request might not match (credential offer does not allow forcredentialSubject
), could we solve this problem by removing an option to includecredentialSubject
in authorization request?if the purpose of
credentialSubject
parameter is data minimization, I think we can achieve the same result as now by allowing the wallet to request subset of claims in credential request. In the end of the day it is up to the issuer to decide which claims to put in the credential.the question is really, is there a need for an Access token to be scoped for the issuance of only a subset of claims? if the answer is yes, we cannot remove
credentialSubject
from the authorization request, but I have not heard that requiremet yet…^ my thinking when I was doing PR #612 trying to address both issues
#1922and#1923, and ultimately ending up addressing only#1923in the PR.. -
@Takahiko Kawasaki , the proposal only introduces identifiers that are “local”/issuer-assigned to the actual entry in the authorisation details, or?
The identifier is not reflected in the credential itself. Or?
Is my understanding correct?
If that’s so, this simplifies the request/response syntax and the proposal can be compatible with whatever solution is proposed for
#1923.1923 is asking about a different thing: I want to request my masters and phd diploma. Both will be of the same type and in the same format. Main difference will be a field expressing the level of education (that will appear in the credentialSubject).
-
SIOP call: taka agreed that changing the authorization details structure as below might work - he will confirm:
Current structure (using the example here) :
[ { "type": "openid_credential", "format": "jwt_vc_json", "credential_definition": { "type": [ "VerifiableCredential", "UniversityDegreeCredential" ], "credentialSubject": { "given_name": {}, "family_name": {}, "degree": {} } } } ]
New structure:
[ { "type": "openid_credential", "format": "jwt_vc_json", "credential_definition": { "type": [ "VerifiableCredential", "UniversityDegreeCredential" ] } } ]
-
Wait, isn’t the purpose of the claims in the authz_details parameter to show a consent screen to the user in such a way that if only a subset of claims is required, the user can see that only this subset will end up in the credential?
Or is this not a use case? In that case, why bother requesting subsets at all (i.e., why have the option to request credentials with subsets in the credential endpoint)?
I guess I’m missing something here, what is it?
-
I don’t think you are missing anything: access token will be scoped to the type (entire claimset), and if necessary, the wallet can scope it down by requesting only the subset at the credential endpoint. Are you saying it is against the philosophy of RAR that enables tightly scoped access tokens (i mentioned this during the call)? in which case, my alternative suggestion would be to keep an option to request a subset using RAR and remove an option to request subset at the credential endpoint. Actually that might work better - it is at the discretion of the issuer to issue whatever claims it decided necessary within the scope of an access token. and we could also potentially use the mechanism in PR #612 where the Issuer returns an identifier in token response that stands for the subset of the claims that the wallet can use in credential endpoint.
-
- changed status to resolved
Migrated to GitHub
- Log in to comment
Hi Taka, just to make sure I understand the rationale. Your are saying comparing the authorization details in the access tokens to the parameter values of the credential request is to complex? I’m asking since the birthdate is not present in the access token so the credential endpoint could detect and refuse to issue the requested credential.