How to issue multiple credentials of the same type
Claim set identifiers are offered as a closed beta preview feature and are not generally available yet. They are enabled per tenant, and their behavior may change in a way that breaks existing integrations before they become generally available. If you are interested in trying this feature, please contact us to have it enabled on your tenant.
This guide walks you through issuing several distinct credentials from a single credential configuration to one user, using claim sets. The worked example throughout is a parent claiming a birth certificate for each of their two children.
Overview
Setting up claim set issuance comprises the following steps:
- Decide where each credential's claims come from.
- Supply claim set identifiers, either by creating a Pre-authorized Code offer or by returning them from an Interaction hook.
- Handle the token response and request each credential.
Prerequisites
- A tenant enabled for claim sets. Claim sets are a closed beta preview feature, so contact us to have them enabled on your tenant.
- A configured mDocs Credential configuration.
- Optionally, a Claims source that can return different data per claim set. You only need one if you want MATTR VII to fetch the claims at issuance time rather than supplying them yourself.
- Either a Pre-authorized Code flow or an Authorization Code flow with an Interaction hook.
Decide where the claims come from
Each credential you issue needs its own claims, and you have two ways to provide them. You can supply them inline with the claim set identifiers, or you can have MATTR VII fetch them from a Claims source at issuance time. A Claims source is not a requirement for using claim sets. Both approaches work with either entry point, and you can combine them.
| Approach | Use it when | What you do |
|---|---|---|
| Inline claims | You already hold the data at the point you create the offer or run the Interaction hook, and it is unlikely to change before the holder claims the credential | Supply the claims for each claim set in claimSets |
| Claims source | The data lives in a system of record, it may change between the offer and issuance, or you would rather not carry it in the request | Omit claimSets, or give each claim set an empty object as its value, and map claimSetId into your Claims source request |
| Both | Most of the credential comes from your system of record, but a few values are known upfront | Supply the known values in claimSets and let the Claims source resolve or override the rest |
Claims are resolved in order, with each step overriding the last: persisted user claims, then
persisted claim set claims, then the claims from your Authentication Provider and the shared
claims on the offer or hook, then the values in claimSets, then your Claims source. A
Claims source therefore has the final say on any claim name it returns.
Query a Claims source by claim set
If you choose to use a Claims source, it needs to know which credential it is being asked
about. When a credential request resolves to a claim set, MATTR VII exposes claimSetId at the
top level of the claims source context. Map it into your request parameters so your endpoint can
return the data for that specific credential.
{
"requestParameters": {
"subjectId": {
"mapFrom": "claims.sub"
},
"recordId": {
"mapFrom": "claimSetId"
}
}
}Your endpoint receives recordId set to the claim set identifier you supplied, and returns
the claims for that one credential.
Mapping claimSetId is opt in. If you do not declare the mapping, your Claims source is called
without it and has no way to tell the credentials apart. If your endpoint requires the parameter
to be present, add a defaultValue. See
error-proofing your queries.
Issue with a Pre-authorized Code offer
To supply claim set identifiers on a Pre-authorized Code offer, use the map form of
credentials. Instead of an array of credential configuration identifiers, pass an object
mapping each configuration identifier to the claim set identifiers you want to issue for it.
POST /v1/openid/offers/pre-authorized{
"credentials": {
"707e920a-f342-443b-ae24-6946b7b5033e": ["bc-2019-114837", "bc-2022-508122"]
},
"userId": "b7e2c8f4-1a2b-4c3d-9e5f-8a7b6c5d4e3f",
"claimSets": {
"707e920a-f342-443b-ae24-6946b7b5033e": {
"bc-2019-114837": {
"givenName": "Ana",
"familyName": "Rivera",
"dateOfBirth": "2019-04-11"
},
"bc-2022-508122": {
"givenName": "Mateo",
"familyName": "Rivera",
"dateOfBirth": "2022-09-30"
}
}
},
"expiresIn": {
"minutes": 5,
"seconds": 0
}
}credentials: A map of mDocs credential configuration identifiers to the claim set identifiers to issue for each. One credential is issued per claim set identifier.claimSets(optional) : The claims for each claim set, keyed by credential configuration identifier and then by claim set identifier. Every claim set identifier referenced here must also be listed against the same configuration incredentials. Where a claim name also appears in the siblingclaimsobject, the claim set value wins.claimsToPersist(optional) : Claim names to persist against the user object. WhenclaimSetsis provided, the listed claims are persisted per claim set, against the user'sclaimSets.
You can omit claimSets entirely and let your Claims source supply the data instead, using
the claimSetId mapping described above, since credentials already names the claim sets to
issue. You can also keep claimSets and set an individual claim set's value to an empty
object, where only some of the claim sets have claims to supply upfront. Use claimSets when
you already hold the data at offer time, and the Claims source when you want it fetched at
issuance time.
Return claim sets from an Interaction hook
In an Authorization Code flow, your Interaction hook decides which claim sets the user is
entitled to after they authenticate. Add a claimSets property to the response JWT payload.
{
"iss": "https://your-interaction-hook.example.com",
"aud": "https://your-tenant.vii.mattr.global",
"state": "hJvfiSp3eEGybd-KmL8ja",
"claimSets": {
"707e920a-f342-443b-ae24-6946b7b5033e": {
"bc-2019-114837": {
"givenName": "Ana",
"familyName": "Rivera",
"dateOfBirth": "2019-04-11"
},
"bc-2022-508122": {
"givenName": "Mateo",
"familyName": "Rivera",
"dateOfBirth": "2022-09-30"
}
}
},
"claimsToPersist": ["givenName", "familyName"],
"iat": 1673911000,
"exp": 1673911060
}MATTR VII takes the claim set identifiers from the keys of claimSets, so this is the only
place an Interaction hook names them. There is no separate list of identifiers, as there is on
a Pre-authorized Code offer.
The claims in a claim set are an overlay, not the whole credential. They are merged over the
shared claims on the same payload and over the claims from your Authentication Provider, and
a Claims source result overrides all of them for any
claim name it returns. Supply only the values that differ between credentials.
To name a claim set without supplying any claims, set its value to an empty object. MATTR VII
binds the identifier and the claims come from the rest of the resolution order, so this is
what you send when your Claims source resolves the data at issuance time using claimSetId.
{
"iss": "https://your-interaction-hook.example.com",
"aud": "https://your-tenant.vii.mattr.global",
"state": "hJvfiSp3eEGybd-KmL8ja",
"claimSets": {
"707e920a-f342-443b-ae24-6946b7b5033e": {
"bc-2019-114837": {},
"bc-2022-508122": {}
}
},
"iat": 1673911000,
"exp": 1673911060
}If the hook names a credential configuration that was not requested, or one the
user is not authorized for, the interaction fails and the wallet receives an access_denied
error.
Request each credential
Once the wallet redeems the code, the token response carries authorization_details with one
credential identifier per claim set.
{
"access_token": "KrrFP8GUeddJJtj7EF-4ugdvCl-dDdWwOqvAbvYsmfy",
"token_type": "Bearer",
"expires_in": 900,
"scope": "mso_mdoc:org.iso.18013.5.1.mDL",
"authorization_details": [
{
"type": "openid_credential",
"credential_configuration_id": "707e920a-f342-443b-ae24-6946b7b5033e",
"credential_identifiers": [
"c3d4e5f6-a7b8-9012-cdef-123456789012",
"d4e5f6a7-b8c9-0123-def1-234567890123"
]
}
]
}The wallet makes one credential request per identifier, sending credential_identifier
instead of credential_configuration_id.
{
"credential_identifier": "c3d4e5f6-a7b8-9012-cdef-123456789012",
"proofs": {
"jwt": ["eyJhbGciOiJFUzI1NiIsInR5cCI6Im9wZW5pZDR2Y2ktcHJvb2Yrand0..."]
}
}The response returns the issued credential along with credential_bundle_id. Treat it as an
opaque value, and use it to correlate the credential back to the claim set it was issued for.
credential_identifier and credential_configuration_id are mutually exclusive, and exactly
one must be present. When a grant binds only one claim set for a configuration,
credential_configuration_id on its own still works, so existing integrations are unaffected.
Find credentials by claim set
Once credentials are issued, you can find them again by the claim set identifier they were issued for. This is how you locate the credential holding a particular record, for example to update it in place after the record changes.
POST /v1/users/credential-bundles/search{
"claimSetId": "bc-2019-114837",
"limit": 100
}claimSetId(optional) : Return only the credential bundles issued for this claim set identifier.state(optional) : Filter by bundle state. Defaults to["active"]when omitted, and an empty array is rejected.limitandcursor(optional) : Page through the results. Each response carries anextCursorwhen more results are available.
Unlike the other credential bundle endpoints, this search is not scoped to a single user or
credential reference. It covers every credential bundle in your tenant, so it also answers
"which holder has the credential for this record". Each result carries the
credentialBundleId you need to create an update offer against it.
Handling errors
| Error description | Cause |
|---|---|
credential_configuration_id is bound to multiple claim sets, credential_identifier is required | The request used credential_configuration_id while the grant binds several claim sets. Send credential_identifier instead |
credential_identifier is not bound to the grant | The identifier was not issued for this grant, or no longer resolves |
credential_configuration_id is not bound to the grant | The configuration was not part of the authorized scope |
claimSets requires the map form of credentials | claimSets was sent on an offer whose credentials is still an array |
claimSets references unknown claim set id | A claim set identifier in claimSets is not listed against the same configuration in credentials |
claim sets feature is not enabled | The tenant is not enabled for claim sets. Claim sets are a closed beta preview feature, so contact us to have them enabled on your tenant |
What's next?
How would you rate this page?
Last updated on