Basics

The basics needed for every use case.

Subject registration

The first thing you need to do is create some public/private key material for your subjects. We use the term subjects for everything related to vendors, tenants, organizations and clients. Behind every subject there can be multiple DIDs (decentralized identifiers). For most of the interactions with the Nuts node, you'll be using the subject identifier.

Create a subject

POST /internal/vdr/v2/subject

{
  "subject": "my_subject_identifier"
}

See API

The subject in the POST body is optional. If not given, the subject identifier will be generated for you and returned in the result:

{
  "subject": "my_subject_identifier",
  "documents": [
    {
      "id": "did:nuts:B8PUHs2AUHbFF1xLLK4eZjgErEcMXHxs68FteY7NDtCY",
      ...
    },
    {
      "id": "did:web:example.com:iam:657f064a-ebef-4f0f-aa87-88ed32db3142",
      ...
    }
  ]
}

You can now use these DIDs to issue and receive Verifiable Credentials.

List DIDs

For some operations you still need DIDs. Issuing credentials is an example. You can find your subject's DIDs by calling the following API:

GET /internal/vdr/v2/subject/my_subject_identifier

Result:

[
  "did:nuts:B8PUHs2AUHbFF1xLLK4eZjgErEcMXHxs68FteY7NDtCY",
  "did:web:example.com:iam:657f064a-ebef-4f0f-aa87-88ed32db3142"
]

Obtaining credentials

After you created a subject with some DID documents, it's time to give meaning to these identifiers. Without Verifiable Credentials, DIDs are just useless identifiers. They receive meaning when another party issues credentials to one of those DIDs. Attestations in the credentials will later be used for authentication & authorization.

Issue a Verifiable Credential

With the Nuts node, you can issue any kind of credential from one of your subjects to any resolvable DID (did:nuts, did:web, did:key and did:jwk). What the meaning of the credential is and how it can be used is determined by the use case.

POST /internal/vcr/v2/issuer/vc

{
  "@context": "https://nuts.nl/credentials/v1",
  "type": "NutsOrganizationCredential",
  "issuer": "did:web:example.com:iam:issuer",
  "credentialSubject": {
    "id": "did:web:example.com:iam:657f064a-ebef-4f0f-aa87-88ed32db3142",
    "organization": {
        "name": "Care bears",
        "city": "Care town"
    }
  },
  "withStatusList2021Revocation": true
}

See API

The withStatusList2021Revocation option allows for credential revocation.

Response:

{
  "@context":[
    "https://nuts.nl/credentials/v1",
    "https://w3id.org/vc/status-list/2021/v1",
    "https://w3c-ccg.github.io/lds-jws2020/contexts/lds-jws2020-v1.json",
    "https://www.w3.org/2018/credentials/v1"
  ],
  "type":["NutsOrganizationCredential","VerifiableCredential"],
  "id":"did:web:example.com:iam:issuer#f2fb02d3-6216-47b9-8e54-b30438a1090e",
  "issuanceDate":"2024-09-09T14:09:50.901123+02:00",
  "issuer":"did:web:example.com:iam"issuer",
  "credentialStatus":{
    "id":"https://example.com/statuslist/did:web:example.com:iam:issuer/1#5",
    "statusListCredential":"https://example.com/statuslist/did:web:example.com:iam:issuer/1",
    "statusListIndex":"5",
    "statusPurpose":"revocation",
    "type":"StatusList2021Entry"
  },
  "credentialSubject":{
    "id":"did:web:example.com:iam:657f064a-ebef-4f0f-aa87-88ed32db3142",
    "organization":{
      "name":"Care bears",
      "city":"Care town"
    }
  },
  "proof":{...}
}

Load a Verifiable Credential

The Nuts node currently doesn't support a new (Nuts network is deprecated) method for exchanging Verifiable Credentials from issuer to holder. The issuer will have to send the credential to the holder out-of-band. The holder can then upload the credential:

POST /internal/vcr/v2/holder/did:web:example.com:iam:657f064a-ebef-4f0f-aa87-88ed32db3142/vc

{
  "@context":[
    "https://nuts.nl/credentials/v1",
    "https://w3id.org/vc/status-list/2021/v1",
    "https://w3c-ccg.github.io/lds-jws2020/contexts/lds-jws2020-v1.json",
    "https://www.w3.org/2018/credentials/v1"
  ],
  "type":["NutsOrganizationCredential","VerifiableCredential"],
  "id":"did:web:example.com:iam:issuer#f2fb02d3-6216-47b9-8e54-b30438a1090e",
  "issuanceDate":"2024-09-09T14:09:50.901123+02:00",
  "issuer":"did:web:example.com:iam"issuer",
  "credentialStatus":{
    "id":"https://example.com/statuslist/did:web:example.com:iam:issuer/1#5",
    "statusListCredential":"https://example.com/statuslist/did:web:example.com:iam:issuer/1",
    "statusListIndex":"5",
    "statusPurpose":"revocation",
    "type":"StatusList2021Entry"
  },
  "credentialSubject":{
    "id":"did:web:example.com:iam:657f064a-ebef-4f0f-aa87-88ed32db3142",
    "organization":{
      "name":"Care bears",
      "city":"Care town"
    }
  },
  "proof":{...}
}

Service activation

Before another party can interact with you, they will first need to find your endpoints. The discovery service in the Nuts node can help you. The Nuts node can act as both server and client. From a use case definition you'll receive the following information/files:

Example:

{
   "id": "coffeecorner",
   "did_methods": ["web", "nuts"],
   "endpoint": "https://example.com/discovery/coffeecorner",
   "presentation_max_validity": 36000,
   "presentation_definition": {
     "id": "coffeecorner2024",
     "format": {
       "ldp_vc": {
         "proof_type": [
           "JsonWebSignature2020"
         ]
       },
       "jwt_vp": {
         "alg": ["ES256"]
       }
     },
     "input_descriptors": [
       {
         "id": "NutsOrganizationCredential",
         "constraints": {
           "fields": [
             {
               "path": [
                 "$.type"
               ],
               "filter": {
                 "type": "string",
                 "const": "NutsOrganizationCredential"
               }
             },
             {
               "id": "organization_name",
               "path": [
                 "$.credentialSubject.organization.name"
               ],
               "filter": {
                 "type": "string"
               }
             },
             {
               "path": [
                 "$.credentialSubject.organization.city"
               ],
               "filter": {
                 "type": "string"
               }
             }
           ]
         }
       }
     ]
   }
 }

The constraint for organization name (organization_name) can be used as search parameter.

Activate service for subject

POST /internal/discovery/v1/coffeecorner/my_subject_identifier

{
  "registrationParameters": {
    "fhir": "https://api.example.com/fhir",
    "contact": "alice@example.com"
  }
}

As you can see, contact information can also be added. Follow the use case requirements.

The response can be a 200 OK which means the service has been activated immediately. It can also be a 202 Accepted which means that the Nuts node received your request but was unable to send the activation to the discovery server. The Nuts node will retry periodically. Others can't find you yet. The logs will keep you posted.

To know if you're findable for others, you can perform a search on the registered subject.

Service search

You can find participants of a use case via their registered services. A use case defines the service identifier and which credentials are required through the presentation definition. Any constraint in the presentation definition with an identifier can also be used in the search query.

GET /internal/discovery/v1/coffeecorner?organization_name=Care* 

Matching is case-insensitive and an * can be used as wildcard. (Similar to the SQL LIKE %)

Some valid examples for query parameters:

See API

Result:

[
  {
    "id":"did:web:example.com:iam:657f064a-ebef-4f0f-aa87-88ed32db3142#049fb56e-1ba8-4e9e-a7af-0071342a1378",
    "credential_subject_id":"did:web:example.com:iam:657f064a-ebef-4f0f-aa87-88ed32db3142",
    "vp":...
    "fields": {
      "organization_name":"Care Bears"
    },
    "registrationParameters": {
      "authServerURL":"https://example.com/oauth2/other_subject_identifier",
      "fhir": "https://api.example.com/fhir",
      "contact": "alice@example.com"
    }
  }
]

As you can see in the search results:

Access tokens

After finding a service endpoint to interact with, it's time to request an access token. You can request one via your own Nuts node:

POST /internal/auth/v2/my_subject_identifier/request-service-access-token

{
  "authorization_server": "https://example.com/oauth2/other_subject_identifier",
  "scope": "coffee",
  "token_type": "Bearer",
  "credentials": [...]
}

The scope is mapped by a policy file to a presentation definition. Policy files are provided by the use case. If your wallet contains the correct credentials according to the presentation definition, an access token will be given:

{
  "access_token": "ciOiJSUzI1NiIsInR5cCI6Ikp",
  "token_type": "Bearer",
  "expires_in": 3600,
}

The access_token can then be put in the HTTP Authorization header.

Authentication & authorization

When a request comes in at your resource endpoint, an access token should be available in the HTTP Authorization header. You can validate the token by calling:

POST /internal/auth/v2/accesstoken/introspect

token=ciOiJSUzI1NiIsInR5cCI6Ikp

Note: the content-type of this call is application/x-www-form-urlencoded.

And the result:

{
  "active":true,  
  "iss": "https://example.com/oauth2/other_subject_identifier",
  "client_id": "https://example.com/oauth2/my_subject_identifier",
  "scope": "coffee",
  "organization_name":"Care Bears"
}

Like with the discovery service, any constraint in the presentation definition of the policy file is also added as key/value pair to the introspection result. This is the mechanism to use attestations from presented credentials for authorization purposes.

The token introspection result is the last thing the Nuts node can do for you. From this point you have to apply the authorization policy...