Build an app

Our example app: My Move

If you have ever moved house, you know that it's a hassle to notify every company or institution of your address change. If you don't, your mail will keep coming to the address you've previously lived at.

That is why a group of friends decided to found a start-up about an app called My Move: a platform that people can use to easily notify companies and institutions of their address change.

Using My Move, you can easily send your new address to the companies of your choice.

Using My Move, you can easily send your new address to the companies of your choice.

In this tutorial...

In this tutorial, we show you how My Move can...

  • Create Solid accounts for new users
  • Let users with a Solid account log in
  • Ask access to data
  • Ask a user to share access with another party

My Move implements two user stories with the following UX:

As a user, I can login or register so I can share my address

As a user, I can give others access to my data so I can easily share my address

Step 1: Get a WebID for your app with a custom domain and configure your DNS

First things first. Your app needs a WebID. Not only to access data in other parties' WebID's but also to get access to the use.id APIs. The developers of My Move decided to go with https://webid.my-move.app/ and https://sandbox.webid.my-move.app/ for testing purposes.

To get a WebID for your app, reach out to your Digita account manager or contact support at [email protected].

Once your Digita account manager has configured your WebID(s), you need to configure your DNS settings.

The My Move developers have added the following records:

webid.my-move.app           CNAME	useid-customer.com
sandbox.webid.my-move.app   CNAME	sandbox-useid-customer.com

Step 2: Authenticate your app

Many users do not have a Solid account yet. My Move needs to keep this in mind and decides to let users easily create a use.id Solid account via the My Move app. This is possible using the use.id management API. The use.id management API requires a machine to machine Solid identity token.

Solid identity tokens are DPoP you first need to To account yet. have access needs to retrieve a machine to machine (identity) token from our IDP using the OAuth2.0 client credentials flow. You can discover our token endpoint using the /.well-known/openid-configuration endpoint.

📘

Solid Identity Tokens & DPoP

Solid identity tokens are DPoP bound. This means that for each user, you first have to generate a public/private key pair. Next, you have to sign a DPoP proof with the private key of this key pair and send it to the token endpoint. The token that you will receive will be bound to your public/private keypair. Whenever you want to use your token to access a Solid storage, you have to include this DPoP proof.

Please take a look at the detailed guides and recipes for more information about DPoP.

Below you can find an example of the request and response made to our token endpoint. This request includes the DPoP header containing the DPoP proof that you signed with the private key you've just generated.

Request

POST /oauth/token HTTP/1.1
Host: idp.sandbox-use.id
Content-Type: application/x-www-form-urlencoded
DPoP: eyJhbGciOiJFUzI1NiIsImp3ayI6eyJhbGciOiJFUzI1NiIsImNydiI6IlAtMjU2Iiwia3R5IjoiRUMiLCJ4IjoiTm1WNDZQQ3NKd0E5cXB1bjF5S29yTHR6MWh2TjJTZko3eHRTYlJOUlAwdyIsInkiOiIzTDI3TjRONFd3LVBBaVd6VXZqWjZRcW96b2RSRGxSYkREREFTbTVwZ1dvIn0sInR5cCI6ImRwb3Arand0In0.eyJhdGgiOm51bGwsImh0bSI6IlBPU1QiLCJodHUiOiJodHRwczovL2lkcC5zYW5kYm94LXVzZS5pZC9vYXV0aC90b2tlbiIsImlhdCI6MTcwNzI5OTU5MSwianRpIjoiNzE5ZTk3YzAtODVjMC00ZjA5LTgxMWUtNzA4OTUxMjBkNGQ4In0.w6XQclO1b9o_vCPbgxupXwhukTNyF0z9qDj9xtskE04rb80ISfBUMIdU7H5unFwpeI6ErdoO_TYyJH-df7vaRg
X-Correlation-ID: 5ed0272b-615a-41ca-ac83-6aa6965d02a1
Content-Length: 161

grant_type=client_credentials&client_id=https%3A%2F%2Fwebid.sandbox-my-move.app%2F&client_secret=CmaAV17tANBeg1G4FneEFhz-M8fF6EGrpuGVSYl5EKJFrL3jAo9dlLw1IM2imVTq

Response

HTTP/1.1 200 OK
Content-Type: application/json
X-Request-ID: 2e7a915e-6b4e-4e7a-a2f3-54fe3e7740aa
X-Correlation-ID: 5ed0272b-615a-41ca-ac83-6aa6965d02a1

{
  "id_token":"eyJhbGciOiJFUzI1NiIsImtpZCI6ImQ3NGMxZGZlLTFhNDEtNGEyMC1hNDJkLWZmYzBmMmJkZGE5OCIsInR5cCI6Imp3dCJ9.eyJzdWIiOiJodHRwczovL3dlYmlkLnNhbmRib3gtbXktbW92ZS5hcHAvIiwic3ViX3dlYmlkIjoiaHR0cHM6Ly93ZWJpZC5zYW5kYm94LW15LW1vdmUuYXBwLyIsIndlYmlkIjoiaHR0cHM6Ly93ZWJpZC5zYW5kYm94LW15LW1vdmUuYXBwLyIsImF1ZCI6WyJodHRwczovL3dlYmlkLnNhbmRib3gtbXktbW92ZS5hcHAvIiwic29saWQiXSwiaXNzIjoiaHR0cHM6Ly9pZHAuc2FuZGJveC11c2UuaWQvIiwiYXpwIjoiaHR0cHM6Ly93ZWJpZC5zYW5kYm94LW15LW1vdmUuYXBwLyIsImF6cF93ZWJpZCI6Imh0dHBzOi8vd2ViaWQuc2FuZGJveC1teS1tb3ZlLmFwcC8iLCJpYXQiOjE3MDcyOTk1OTIsImV4cCI6MTcwNzMwMzE5MiwiY25mIjp7ImprdCI6ImJ0SUVlM21GbmpERW82VVpxeFh4RHhSUHhTdWpIZWlUQjV5U25sMVpUT28ifX0.etk7b8xS56cIgVtS7c0zXp_kOwoh8PnXN64QyGh_qaw9xEiZ7HrMSrADb935qYhKqdxBawkb8gXLtAI89FnenA",
  "token_type":"DPoP",
  "expires_in":3600
}

You now have a machine to machine identity token that is bound to your public/private key pair. You will need to reuse this public/private key pair to generate a new DPoP proof for each request that you send to a Solid storage server along with the token.

When you use this token for a use.id management API, you do not have to use DPoP.

Tip: you can use jwt.io to inspect the information in the tokens.

📘

X-Request-ID & X-Correlation-ID

Every request and response gets a Request ID and Correlation ID. You can use these identifiers when asking us for help. This way, we can easily see what went wrong. Take a look at our detailed guide for more info.

Step 3: Create a use.id account for a user via the management API

Now that the My Move app is authenticated, My Move is able to create new use.id Solid accounts.

Request

POST /provision HTTP/1.1
Host: api.sandbox-use.id
Authorization: DPoP eyJhbGciOiJFUzI1NiIsImtpZCI6ImQ3NGMxZGZlLTFhNDEtNGEyMC1hNDJkLWZmYzBmMmJkZGE5OCIsInR5cCI6Imp3dCJ9.eyJzdWIiOiJodHRwczovL3dlYmlkLnNhbmRib3gtbXktbW92ZS5hcHAvIiwic3ViX3dlYmlkIjoiaHR0cHM6Ly93ZWJpZC5zYW5kYm94LW15LW1vdmUuYXBwLyIsIndlYmlkIjoiaHR0cHM6Ly93ZWJpZC5zYW5kYm94LW15LW1vdmUuYXBwLyIsImF1ZCI6WyJodHRwczovL3dlYmlkLnNhbmRib3gtbXktbW92ZS5hcHAvIiwic29saWQiXSwiaXNzIjoiaHR0cHM6Ly9pZHAuc2FuZGJveC11c2UuaWQvIiwiYXpwIjoiaHR0cHM6Ly93ZWJpZC5zYW5kYm94LW15LW1vdmUuYXBwLyIsImF6cF93ZWJpZCI6Imh0dHBzOi8vd2ViaWQuc2FuZGJveC1teS1tb3ZlLmFwcC8iLCJpYXQiOjE3MDcyOTk1OTIsImV4cCI6MTcwNzMwMzE5MiwiY25mIjp7ImprdCI6ImJ0SUVlM21GbmpERW82VVpxeFh4RHhSUHhTdWpIZWlUQjV5U25sMVpUT28ifX0.etk7b8xS56cIgVtS7c0zXp_kOwoh8PnXN64QyGh_qaw9xEiZ7HrMSrADb935qYhKqdxBawkb8gXLtAI89FnenA
Slug: john-5ed0272b
Content-Type: application/json
DPoP: eyJhbGciOiJFUzI1NiIsImp3ayI6eyJhbGciOiJFUzI1NiIsImNydiI6IlAtMjU2Iiwia3R5IjoiRUMiLCJ4IjoiTm1WNDZQQ3NKd0E5cXB1bjF5S29yTHR6MWh2TjJTZko3eHRTYlJOUlAwdyIsInkiOiIzTDI3TjRONFd3LVBBaVd6VXZqWjZRcW96b2RSRGxSYkREREFTbTVwZ1dvIn0sInR5cCI6ImRwb3Arand0In0.eyJhdGgiOm51bGwsImh0bSI6IlBPU1QiLCJodHUiOiJodHRwczovL2FwaS5zYW5kYm94LXVzZS5pZC9wcm92aXNpb24iLCJpYXQiOjE3MDcyOTk1OTIsImp0aSI6IjMyOTMzZmJlLTRiYzUtNDJkYi1hMDYyLTUwZGFlOWYxMjk5MCJ9.X9vU4Tt8JtcnoncLE-XtyqfbuB2U-nCg-4XAB5kQeccoNO58SQBHuQEz1Po7XcXarZcUIP_3lVH-2EIyr8lP5w
X-Correlation-ID: 5ed0272b-615a-41ca-ac83-6aa6965d02a1
Content-Length: 43

{"email": "[email protected]"}

Response

HTTP/1.1 201 Created
location: https://sandbox-use.id/john-5ed0272b

When the account was successfully created, you will receive the newly created WebID in the response's location header.

Step 4: Ask the user to login

Now, My Move needs the user to authenticate himself/herself and have them share access to a certain type of data. For this, My Move initiates the OAuth2.0 Authorization Code Flow with PKCE and provides a patch request in an optional query parameter.

Let us first take a look at the patch request.

data = {
  "conditions": {
    "tos_uri": "https://example.com/tos",
    "policy_uri": "https://example.com/policy",
    "legal_basis_uri": [ "https://example.com/legal-basis" ],
    "purpose_uri": [ "https://example.com/purpose" ],
  },
  "add":{
    "access": {
      "subject_type_combo": [
        {
          "applies_to_subject_uri": "{auth-request-subject}",
          "applies_to_type_uri": "https://test-types.com/address",
          "allowed_subject_uri": "{auth-request-subject}",
          "allowed_azp_uri": "https://webid.sandbox-my-move.app/",
          "allowed_issuer_uri": "*",
          "allowed_access_mode": "read",
          "storage": "*",
        },
        {
          "applies_to_subject_uri": "{auth-request-subject}",
          "applies_to_type_uri": "https://test-types.com/address",
          "allowed_subject_uri": "{auth-request-subject}",
          "allowed_azp_uri": "https://webid.sandbox-my-move.app/",
          "allowed_issuer_uri": "*",
          "allowed_access_mode": "write",
          "storage": "*",
        },
        {
          "applies_to_subject_uri": "{auth-request-subject}",
          "applies_to_type_uri": "https://test-types.com/address",
          "allowed_subject_uri": "{auth-request-subject}",
          "allowed_azp_uri": "https://webid.sandbox-my-move.app/",
          "allowed_issuer_uri": "*",
          "allowed_access_mode": "create",
          "storage": "*",
        },
      ],
      # MUST INCLUDE EVEN IF EMPTY
      "resource": [],
    },
  },
  # MUST INCLUDE EVEN IF EMPTY
  "remove":{
    "access":{
      # MUST INCLUDE EVEN IF EMPTY
      "subject_type_combo": [],
      # MUST INCLUDE EVEN IF EMPTY
      "resource": [],
    },
  },
}

# object -> json string
dump = json.dumps(data, separators=(',', ':'))
# string -> bytes
in_bytes = dump.encode('utf-8')
# brotli encode / compress
compressed = brotli.compress(in_bytes)
# base64 encode
encoded = base64.b64encode(compressed).decode('ascii')
# this values should be included as the 'solid_patch' query parameter

In this example, you can see that My Move asks access to an address. It also provides a placeholder for the subject as this subject has not yet known.

This document should then be Brotli encoded (the example includes JavaScript code as a reference).

Now, we can redirect the user to authenticate and wait for a callback.

Redirect User

First, go to the .well-known/openid-configuration endpoint to discover the location of the OAuth2.0 authorization endpoint.

Next, redirect the user to this location:

https://idp.sandbox-use.id/oauth/authorize?
  client_id=https%3A%2F%2Fwebid.sandbox-my-move.app%2F&
  response_type=code&
  state=5ed0272b-615a-41ca-ac83-6aa6965d02a1&
  redirect_uri=https%3A%2F%2Fsandbox-my-move.app%2F&
  code_challenge_method=S256&
  code_challenge=hjooUY_1tBlE_dBuCKGUK8XuSRrc_zNByH-roC5sIXA&
  email=john-5ed0272b%40digita.ai
  &solid_patch=G3cEAIzEOCbxRlHEQRBtbtpvjJ7GQeJ5McghXkypEptFp6vP%2F1zz%2BYHSr7sGC2usoMilbjQwnfIAZ5sNIl9RQrEz83Q%2BY0%2BD%2By9w0PzWXjOyOFNJO0UJCBfwNhfKCkqhWMozm1wOQlBFAgLGZ58C69BgKX0904xPQoepWTX0KkfHMGgpgTZXlIRRGtbbKYQ5Mv4yWa3ofSNRm82Ccksq3IPIZFnHk5i8s794SZ8j5rS8iGz0V65PfhP5aaVsG9WDU%2Ff9MsikmFqmcGDheEA1EvyvmxBDs%2Fm3FG4jo%2BIHbX2AYX9oJUsn4Li9OQ%3D%3D

The user now receives an email with a link they can follow. If a 'solid_patch' parameter was provided, the user is asked to accept, if the parameter was not provided, the user is immediately redirected to the redirect uri that was requested in the call above.

Redirect URI

When the user is authenticated, s/he is redirected back using the following URI. This URI must be present in the WebID document of the app and is used to authenticate the app.

https://sandbox-my-move.app/?
  code=4e237c6f-0a5f-4327-abac-4a9188d91a37&
  patch_decision=true&
  patch_success=true&
  state=5ed0272b-615a-41ca-ac83-6aa6965d02a1

As you can see, this url also includes info about whether or not the user has accepted to share the requested data with the My Move app (i.e. patch_decision) and whether the user's primary storage was able to process this request (i.e. patch_success).

Now, the My Move app continues with the Authorization Code flow with PKCE and requests a token using the code.

Request

POST /oauth/token HTTP/1.1
Host: idp.sandbox-use.id
Content-Type: application/x-www-form-urlencoded
DPoP: eyJhbGciOiJFUzI1NiIsImp3ayI6eyJhbGciOiJFUzI1NiIsImNydiI6IlAtMjU2Iiwia3R5IjoiRUMiLCJ4IjoiTm1WNDZQQ3NKd0E5cXB1bjF5S29yTHR6MWh2TjJTZko3eHRTYlJOUlAwdyIsInkiOiIzTDI3TjRONFd3LVBBaVd6VXZqWjZRcW96b2RSRGxSYkREREFTbTVwZ1dvIn0sInR5cCI6ImRwb3Arand0In0.eyJhdGgiOm51bGwsImh0bSI6IlBPU1QiLCJodHUiOiJodHRwczovL2lkcC5zYW5kYm94LXVzZS5pZC9vYXV0aC90b2tlbiIsImlhdCI6MTcwNzI5OTY0MCwianRpIjoiZjliN2E4OTMtZTMxMS00M2JmLWFjNjktNGNjMGIxNWVjN2ZjIn0.al9kpB0VdZKVUEiUiQpYhWXqyp3t94SSyCbRDYnJ_XpRq8qXiJVgBeB6yOfAtM4azODS-TzSK7zq_KooxNVUHw
X-Correlation-ID: 5ed0272b-615a-41ca-ac83-6aa6965d02a1
Content-Length: 242

grant_type=authorization_code&client_id=https%3A%2F%2Fwebid.sandbox-my-move.app%2F&code=4e237c6f-0a5f-4327-abac-4a9188d91a37&code_verifier=zo6yP8H9te4I0lk2Uclcry47yPbTT9jRbdnIZPdMUfazH5iD8vkNw&redirect_uri=https%3A%2F%2Fsandbox-my-move.app%2F

Response

HTTP/1.1 200 OK
Content-Type: application/json
X-Request-ID: 4676e235-31be-4475-abb3-c9599636bb09
X-Correlation-ID: 5ed0272b-615a-41ca-ac83-6aa6965d02a1

{
  "id_token":"eyJhbGciOiJFUzI1NiIsImtpZCI6ImQ3NGMxZGZlLTFhNDEtNGEyMC1hNDJkLWZmYzBmMmJkZGE5OCIsInR5cCI6Imp3dCJ9.eyJzdWIiOiJodHRwczovL3NhbmRib3gtdXNlLmlkL2pvaG4tNWVkMDI3MmIiLCJzdWJfd2ViaWQiOiJodHRwczovL3NhbmRib3gtdXNlLmlkL2pvaG4tNWVkMDI3MmIiLCJ3ZWJpZCI6Imh0dHBzOi8vc2FuZGJveC11c2UuaWQvam9obi01ZWQwMjcyYiIsImF1ZCI6WyJodHRwczovL3dlYmlkLnNhbmRib3gtbXktbW92ZS5hcHAvIiwic29saWQiXSwiaXNzIjoiaHR0cHM6Ly9pZHAuc2FuZGJveC11c2UuaWQvIiwiYXpwIjoiaHR0cHM6Ly93ZWJpZC5zYW5kYm94LW15LW1vdmUuYXBwLyIsImF6cF93ZWJpZCI6Imh0dHBzOi8vd2ViaWQuc2FuZGJveC1teS1tb3ZlLmFwcC8iLCJpYXQiOjE3MDcyOTk2NDAsImV4cCI6MTcwNzMwMzI0MCwiY25mIjp7ImprdCI6ImJ0SUVlM21GbmpERW82VVpxeFh4RHhSUHhTdWpIZWlUQjV5U25sMVpUT28ifX0.8JueCCz3kyOc3bRVrmbqqgBNqAabXAAiYiy_a3_FNfNLaeWfPzPlm31b-Do3mQa4dXP133-FE-uiE076daB_5g",
  "token_type":"DPoP",
  "expires_in":3600
 }

The My Move app now has a human-to-machine token for this person it can use to retrieve, edit or delete specific data with.

When you inspect this token using jwt.io, you can see both the user (sub) and app (azp) are authenticated.

Step 5: Create a resource

Now that you have a human-to-machine token and access, you can create a resource.

If you do not know where you should create this resource, it is best to rely on the primary-storage of the user. This location probably allows write requests (if you have access). You can discover this location in the user's WebID profile document. Here, the primary storage is the one of use.id.

When you create a resource, you have to do a POST request. You cannot choose a resource name.

Request

POST /resource?type=https%3A%2F%2Ftest-types.com%2Faddress&subject=https%3A%2F%2Fsandbox-use.id%2Fjohn-5ed0272b HTTP/1.1
Host: storage.sandbox-use.id
Authorization: DPoP eyJhbGciOiJFUzI1NiIsImtpZCI6ImQ3NGMxZGZlLTFhNDEtNGEyMC1hNDJkLWZmYzBmMmJkZGE5OCIsInR5cCI6Imp3dCJ9.eyJzdWIiOiJodHRwczovL3NhbmRib3gtdXNlLmlkL2pvaG4tNWVkMDI3MmIiLCJzdWJfd2ViaWQiOiJodHRwczovL3NhbmRib3gtdXNlLmlkL2pvaG4tNWVkMDI3MmIiLCJ3ZWJpZCI6Imh0dHBzOi8vc2FuZGJveC11c2UuaWQvam9obi01ZWQwMjcyYiIsImF1ZCI6WyJodHRwczovL3dlYmlkLnNhbmRib3gtbXktbW92ZS5hcHAvIiwic29saWQiXSwiaXNzIjoiaHR0cHM6Ly9pZHAuc2FuZGJveC11c2UuaWQvIiwiYXpwIjoiaHR0cHM6Ly93ZWJpZC5zYW5kYm94LW15LW1vdmUuYXBwLyIsImF6cF93ZWJpZCI6Imh0dHBzOi8vd2ViaWQuc2FuZGJveC1teS1tb3ZlLmFwcC8iLCJpYXQiOjE3MDcyOTk2NDAsImV4cCI6MTcwNzMwMzI0MCwiY25mIjp7ImprdCI6ImJ0SUVlM21GbmpERW82VVpxeFh4RHhSUHhTdWpIZWlUQjV5U25sMVpUT28ifX0.8JueCCz3kyOc3bRVrmbqqgBNqAabXAAiYiy_a3_FNfNLaeWfPzPlm31b-Do3mQa4dXP133-FE-uiE076daB_5g
Content-Type: text/plain
DPoP: eyJhbGciOiJFUzI1NiIsImp3ayI6eyJhbGciOiJFUzI1NiIsImNydiI6IlAtMjU2Iiwia3R5IjoiRUMiLCJ4IjoiTm1WNDZQQ3NKd0E5cXB1bjF5S29yTHR6MWh2TjJTZko3eHRTYlJOUlAwdyIsInkiOiIzTDI3TjRONFd3LVBBaVd6VXZqWjZRcW96b2RSRGxSYkREREFTbTVwZ1dvIn0sInR5cCI6ImRwb3Arand0In0.eyJhdGgiOiJSbUxYcWVmMU8xWHJaeUVROXFBRkVBMy00WjB0ckd2Y1JDSkVsQ2YxQ05VIiwiaHRtIjoiUE9TVCIsImh0dSI6Imh0dHBzOi8vc3RvcmFnZS5zYW5kYm94LXVzZS5pZC9yZXNvdXJjZSIsImlhdCI6MTcwNzI5OTY0MCwianRpIjoiZjQ4OGE4OWUtYTAyNi00MzM4LWExOGItYWFlNGZkZDBiOGI1In0.mrQ0tV49DdR_1Gk4ZJzXn9NuV_xS6qC5Ty_KBFIlwEif6l99v2XJEsraQo2y9h95X8kDYnf8AnAUUIgnUcVHHw
X-Correlation-ID: 5ed0272b-615a-41ca-ac83-6aa6965d02a1
Content-Length: 36

Hello world! This is my test file :)

Response

HTTP/1.1 201 Created
Connection: keep-alive
Location: https://storage.sandbox-use.id/c110f9b2-832f-4a43-a418-f5cfc21c01ad
Data-Subject: https://sandbox-use.id/john-5ed0272b
Data-Type: https://test-types.com/address
ETag: "86fb269d190d2c85f6e0468ceca42a20"
X-Request-ID: 35e7ad96-47ba-4590-86b6-a4381e72a273
X-Correlation-ID: 5ed0272b-615a-41ca-ac83-6aa6965d02a1

The response indicates that the creation of the new document is succesful and returns the name of the resource in the location header.

Step 6: Discover all resources

My Move can also discover which resources it has access to given a certain token. To do so, My Move has to send a request to the /resources endpoint of each of the storages in the user's WebID profile document.

This is an example of the request and response sent to the primary storage of the user.

Request

GET /resources HTTP/1.1
Host: storage.sandbox-use.id
Accept-Encoding: gzip, deflate, br
Authorization: DPoP eyJhbGciOiJFUzI1NiIsImtpZCI6ImQ3NGMxZGZlLTFhNDEtNGEyMC1hNDJkLWZmYzBmMmJkZGE5OCIsInR5cCI6Imp3dCJ9.eyJzdWIiOiJodHRwczovL3NhbmRib3gtdXNlLmlkL2pvaG4tNWVkMDI3MmIiLCJzdWJfd2ViaWQiOiJodHRwczovL3NhbmRib3gtdXNlLmlkL2pvaG4tNWVkMDI3MmIiLCJ3ZWJpZCI6Imh0dHBzOi8vc2FuZGJveC11c2UuaWQvam9obi01ZWQwMjcyYiIsImF1ZCI6WyJodHRwczovL3dlYmlkLnNhbmRib3gtbXktbW92ZS5hcHAvIiwic29saWQiXSwiaXNzIjoiaHR0cHM6Ly9pZHAuc2FuZGJveC11c2UuaWQvIiwiYXpwIjoiaHR0cHM6Ly93ZWJpZC5zYW5kYm94LW15LW1vdmUuYXBwLyIsImF6cF93ZWJpZCI6Imh0dHBzOi8vd2ViaWQuc2FuZGJveC1teS1tb3ZlLmFwcC8iLCJpYXQiOjE3MDcyOTk2NDAsImV4cCI6MTcwNzMwMzI0MCwiY25mIjp7ImprdCI6ImJ0SUVlM21GbmpERW82VVpxeFh4RHhSUHhTdWpIZWlUQjV5U25sMVpUT28ifX0.8JueCCz3kyOc3bRVrmbqqgBNqAabXAAiYiy_a3_FNfNLaeWfPzPlm31b-Do3mQa4dXP133-FE-uiE076daB_5g
DPoP: eyJhbGciOiJFUzI1NiIsImp3ayI6eyJhbGciOiJFUzI1NiIsImNydiI6IlAtMjU2Iiwia3R5IjoiRUMiLCJ4IjoiTm1WNDZQQ3NKd0E5cXB1bjF5S29yTHR6MWh2TjJTZko3eHRTYlJOUlAwdyIsInkiOiIzTDI3TjRONFd3LVBBaVd6VXZqWjZRcW96b2RSRGxSYkREREFTbTVwZ1dvIn0sInR5cCI6ImRwb3Arand0In0.eyJhdGgiOiJSbUxYcWVmMU8xWHJaeUVROXFBRkVBMy00WjB0ckd2Y1JDSkVsQ2YxQ05VIiwiaHRtIjoiR0VUIiwiaHR1IjoiaHR0cHM6Ly9zdG9yYWdlLnNhbmRib3gtdXNlLmlkL3Jlc291cmNlcyIsImlhdCI6MTcwNzI5OTY0MSwianRpIjoiYmVhMGM2NWQtMzI3My00MGFhLThkNzAtZWEyZWY0Y2FhNTY0In0.rACC21jFy3eRo7iGXW8COBMR6WOq93pdOUERFuRITIERhpf5JRWliqwNl_9qbKS0a6tBJw1BNcW2FwbbuLZKCQ
X-Correlation-ID: 5ed0272b-615a-41ca-ac83-6aa6965d02a1

Response

HTTP/1.1 200 OK
Content-Type: application/json
Last-Modified: 2024-02-07T09:54:02.048Z
X-Request-ID: f917c249-03b5-4790-b99d-ca6a972be6a1
X-Correlation-ID: 5ed0272b-615a-41ca-ac83-6aa6965d02a1

{
  "subject_type_combinations":[
    {
      "subject":"https://sandbox-use.id/john-5ed0272b",
      "type":"https://test-types.com/address",
      "allowed_access_for_combination":["read","write","create"],
      "resources":[
        {
          "uri":"https://storage.sandbox-use.id/c110f9b2-832f-4a43-a418-f5cfc21c01ad",
          "content_type":"text/plain",
          "last_modified":"2024-02-07 09:54:01.445512+00",
          "last_modified_by":"sub=\"https://sandbox-use.id/john-5ed0272b\", azp=\"https://webid.sandbox-my-move.app/\", iss=\"https://idp.sandbox-use.id/\"",
          "created_at":"2024-02-07 09:54:01.445512+00",
          "created_by":"sub=\"https://sandbox-use.id/john-5ed0272b\", azp=\"https://webid.sandbox-my-move.app/\", iss=\"https://idp.sandbox-use.id/\"",
          "tags":"",
          "allowed_access_for_resource":["read","write"]
        }
      ]
    }
  ]
}

Check out our detailed guides and API documentation for more information.

Step 7: Retrieve a resource that was discovered

Using the response of the previous request, My Move knows what resource it requires and retreives the resource using the following request.

Request

GET /c110f9b2-832f-4a43-a418-f5cfc21c01ad HTTP/1.1
Host: storage.sandbox-use.id
Authorization: DPoP eyJhbGciOiJFUzI1NiIsImtpZCI6ImQ3NGMxZGZlLTFhNDEtNGEyMC1hNDJkLWZmYzBmMmJkZGE5OCIsInR5cCI6Imp3dCJ9.eyJzdWIiOiJodHRwczovL3NhbmRib3gtdXNlLmlkL2pvaG4tNWVkMDI3MmIiLCJzdWJfd2ViaWQiOiJodHRwczovL3NhbmRib3gtdXNlLmlkL2pvaG4tNWVkMDI3MmIiLCJ3ZWJpZCI6Imh0dHBzOi8vc2FuZGJveC11c2UuaWQvam9obi01ZWQwMjcyYiIsImF1ZCI6WyJodHRwczovL3dlYmlkLnNhbmRib3gtbXktbW92ZS5hcHAvIiwic29saWQiXSwiaXNzIjoiaHR0cHM6Ly9pZHAuc2FuZGJveC11c2UuaWQvIiwiYXpwIjoiaHR0cHM6Ly93ZWJpZC5zYW5kYm94LW15LW1vdmUuYXBwLyIsImF6cF93ZWJpZCI6Imh0dHBzOi8vd2ViaWQuc2FuZGJveC1teS1tb3ZlLmFwcC8iLCJpYXQiOjE3MDcyOTk2NDAsImV4cCI6MTcwNzMwMzI0MCwiY25mIjp7ImprdCI6ImJ0SUVlM21GbmpERW82VVpxeFh4RHhSUHhTdWpIZWlUQjV5U25sMVpUT28ifX0.8JueCCz3kyOc3bRVrmbqqgBNqAabXAAiYiy_a3_FNfNLaeWfPzPlm31b-Do3mQa4dXP133-FE-uiE076daB_5g
DPoP: eyJhbGciOiJFUzI1NiIsImp3ayI6eyJhbGciOiJFUzI1NiIsImNydiI6IlAtMjU2Iiwia3R5IjoiRUMiLCJ4IjoiTm1WNDZQQ3NKd0E5cXB1bjF5S29yTHR6MWh2TjJTZko3eHRTYlJOUlAwdyIsInkiOiIzTDI3TjRONFd3LVBBaVd6VXZqWjZRcW96b2RSRGxSYkREREFTbTVwZ1dvIn0sInR5cCI6ImRwb3Arand0In0.eyJhdGgiOiJSbUxYcWVmMU8xWHJaeUVROXFBRkVBMy00WjB0ckd2Y1JDSkVsQ2YxQ05VIiwiaHRtIjoiR0VUIiwiaHR1IjoiaHR0cHM6Ly9zdG9yYWdlLnNhbmRib3gtdXNlLmlkL2MxMTBmOWIyLTgzMmYtNGE0My1hNDE4LWY1Y2ZjMjFjMDFhZCIsImlhdCI6MTcwNzI5OTY0MSwianRpIjoiNzE3MTk3NGEtMjllZS00NGY0LTg2YjItNTBmZjY1YWNkMjEwIn0.-65U3g0EKe2xZyVWPuv2uvNswEyIAX107p56-AsdY_s3On6X7xaScwOBxbSK4M7Dc_hwan6sorqH_e2cbrR7Hg
X-Correlation-ID: 5ed0272b-615a-41ca-ac83-6aa6965d02a1

Response

HTTP/1.1 200 OK
Content-Type: text/plain
Content-Length: 36
allow: GET, PUT, DELETE
created-at: 2024-02-07T09:54:01.445Z
created-by: sub="https://sandbox-use.id/john-5ed0272b", azp="https://webid.sandbox-my-move.app/", iss="https://idp.sandbox-use.id/"
etag: "86fb269d190d2c85f6e0468ceca42a20"
labels: 
last-modified: Wed, 07 Feb 2024 09:54:02 GMT
last-modified-by: sub="https://sandbox-use.id/john-5ed0272b", azp="https://webid.sandbox-my-move.app/", iss="https://idp.sandbox-use.id/"
data-type: https://test-types.com/address
data-subject: https://sandbox-use.id/john-5ed0272b
vary: Authorization, DPoP, Purpose, Legal-Basis
x-request-id: bfb00348-fc66-4680-b9ae-558bf3e4dcc7
x-correlation-id: 5ed0272b-615a-41ca-ac83-6aa6965d02a1

Hello world! this is my test file :)

As you can see, the response contains plenty of metadata about the resource.

Step 8: Ask the user to share data with another party

In this step, My Move asks the user to share data with ABC Banking. To do this, My Move initiates the authorization code flow again, but with another Patch Document.

data = {
  "conditions": {
    "tos_uri": "https://example.com/tos",
    "policy_uri": "https://example.com/policy",
    "legal_basis_uri": [ "https://example.com/legal-basis" ],
    "purpose_uri": [ "https://example.com/purpose" ],
  },
  "add":{
    "access": {
      "subject_type_combo": [
        {
          "applies_to_subject_uri": "{user-webid}",
          "applies_to_type_uri": "https://test-types.com/address",
          "allowed_subject_uri": "https://webid.sandbox-abc-banking.com/",
          "allowed_azp_uri": "https://webid.sandbox-abc-banking.com/",
          "allowed_issuer_uri": "*",
          "allowed_access_mode": "read",
          "storage": "*",
        },
        {
          "applies_to_subject_uri": "{user-webid}",
          "applies_to_type_uri": "https://test-types.com/address",
          "allowed_subject_uri": "https://webid.sandbox-abc-banking.com/",
          "allowed_azp_uri": "https://webid.sandbox-abc-banking.com/",
          "allowed_issuer_uri": "*",
          "allowed_access_mode": "write",
          "storage": "*",
        },
        {
          "applies_to_subject_uri": "{user-webid}",
          "applies_to_type_uri": "https://test-types.com/address",
          "allowed_subject_uri": "https://webid.sandbox-abc-banking.com/",
          "allowed_azp_uri": "https://webid.sandbox-abc-banking.com/",
          "allowed_issuer_uri": "*",
          "allowed_access_mode": "create",
          "storage": "*",
        },
      ],
      # MUST INCLUDE EVEN IF EMPTY
      "resource": [],
    },
  },
  # MUST INCLUDE EVEN IF EMPTY
  "remove":{
    "access":{
      # MUST INCLUDE EVEN IF EMPTY
      "subject_type_combo": [],
      # MUST INCLUDE EVEN IF EMPTY
      "resource": [],
    },
  },
}

# object -> json string
dump = json.dumps(data, separators=(',', ':'))
# string -> bytes
in_bytes = dump.encode('utf-8')
# brotli encode / compress
compressed = brotli.compress(in_bytes)
# base64 encode
encoded = base64.b64encode(compressed).decode('ascii')
# this values should be included as the 'solid_patch' query parameter

As you can see, this patch document is not to give access to My Move, but rather to give access to ABC Banking.

The actual URIs to which the user is redirected are as follows:

Redirect to authorization endpoint

https://idp.sandbox-use.id/oauth/authorize?
  client_id=https%3A%2F%2Fwebid.sandbox-my-move.app%2F&
  response_type=code&
  state=5ed0272b-615a-41ca-ac83-6aa6965d02a1&
  redirect_uri=https%3A%2F%2Fsandbox-my-move.app%2F&
  code_challenge_method=S256&
  code_challenge=hjooUY_1tBlE_dBuCKGUK8XuSRrc_zNByH-roC5sIXA&
  email=john-5ed0272b%40digita.ai&
  solid_patch=G90EAIzTFfMjhHXPonWpT3yxBe1Bn4hm6evS%2BfenngeU3srY58M%2BYPbqBit52cpcwPpb7ieHIzqh6Kn5bVh6eXEdYpzexpX2ZehlbBZJO3kJiAhgbSmUGZhccSjOLHK9IYJqocIh4otPgVXwgCv19hxmvJNw2JRVi3ZajFjzYqQo2kZCTbmBjfxJjFe4rzoaVFwL34SlVklWZwJ0kPaJjSXmhJmWnzAoBrTOEruZU4tBRdYVFzKc76RpqOIt%2FJnEqWK8ZR29hxw2mwoZDiwtWFKvBP9zf6GjifBrEw%2BrSUoOH48G1nQFA2ttm0%2FL2wE%3D

Redirect URI

https://sandbox-my-move.app/?
  code=424b1b26-fba0-432c-b34a-3f1899a61337&
  patch_decision=true&
  patch_success=true&
  state=5ed0272b-615a-41ca-ac83-6aa6965d02a1

The redirect contains a 'code' parameter that can be used to exchange a new token by My Move, but this is not required since the app already had a valid token.

If the user has accepted the patch request, ABC Banking has access.

Step 9: ABC Banking authenticates with client credentials

Note that ABC Banking has received access with its own WebID in the sub claim of the token. This means that ABC Banking can use a machine to machine token.

To retrieve such a token, ABC Banking uses the client credentials flow.

Request

POST /oauth/token HTTP/1.1
Host: idp.sandbox-use.id
Content-Type: application/x-www-form-urlencoded
DPoP: eyJhbGciOiJFUzI1NiIsImp3ayI6eyJhbGciOiJFUzI1NiIsImNydiI6IlAtMjU2Iiwia3R5IjoiRUMiLCJ4IjoiTm1WNDZQQ3NKd0E5cXB1bjF5S29yTHR6MWh2TjJTZko3eHRTYlJOUlAwdyIsInkiOiIzTDI3TjRONFd3LVBBaVd6VXZqWjZRcW96b2RSRGxSYkREREFTbTVwZ1dvIn0sInR5cCI6ImRwb3Arand0In0.eyJhdGgiOm51bGwsImh0bSI6IlBPU1QiLCJodHUiOiJodHRwczovL2lkcC5zYW5kYm94LXVzZS5pZC9vYXV0aC90b2tlbiIsImlhdCI6MTcwNzI5OTcxMywianRpIjoiMTdkNGVkZDMtOTVmYi00OGM5LWJkMWEtMmI5OGU3NDViZWRhIn0.sOFknuhpvazTyHiX_ZUZ0d-PnGYXNO6vJIdNc8hklNo-P26PUBwMtNtnpUVJ9XJxcts8YSYwQrWQDtEYBKMPbA
X-Correlation-ID: 5ed0272b-615a-41ca-ac83-6aa6965d02a1
Content-Length: 165

grant_type=client_credentials&client_id=https%3A%2F%2Fwebid.sandbox-abc-banking.com%2F&client_secret=4fW7GTL97gHxd3aBKfmoUGyoXKZIbPykaQyJUQUP26N7ihD-DCnQiazeP1m2i7kI

Response

HTTP/1.1 200 OK
Content-Type: application/json
X-Request-ID: e9e3950c-f066-485b-ae28-2255b62b1058
X-Correlation-ID: 5ed0272b-615a-41ca-ac83-6aa6965d02a1

{
  "id_token":"eyJhbGciOiJFUzI1NiIsImtpZCI6ImQ3NGMxZGZlLTFhNDEtNGEyMC1hNDJkLWZmYzBmMmJkZGE5OCIsInR5cCI6Imp3dCJ9.eyJzdWIiOiJodHRwczovL3dlYmlkLnNhbmRib3gtYWJjLWJhbmtpbmcuY29tLyIsInN1Yl93ZWJpZCI6Imh0dHBzOi8vd2ViaWQuc2FuZGJveC1hYmMtYmFua2luZy5jb20vIiwid2ViaWQiOiJodHRwczovL3dlYmlkLnNhbmRib3gtYWJjLWJhbmtpbmcuY29tLyIsImF1ZCI6WyJodHRwczovL3dlYmlkLnNhbmRib3gtYWJjLWJhbmtpbmcuY29tLyIsInNvbGlkIl0sImlzcyI6Imh0dHBzOi8vaWRwLnNhbmRib3gtdXNlLmlkLyIsImF6cCI6Imh0dHBzOi8vd2ViaWQuc2FuZGJveC1hYmMtYmFua2luZy5jb20vIiwiYXpwX3dlYmlkIjoiaHR0cHM6Ly93ZWJpZC5zYW5kYm94LWFiYy1iYW5raW5nLmNvbS8iLCJpYXQiOjE3MDcyOTk3MTMsImV4cCI6MTcwNzMwMzMxMywiY25mIjp7ImprdCI6ImJ0SUVlM21GbmpERW82VVpxeFh4RHhSUHhTdWpIZWlUQjV5U25sMVpUT28ifX0.0wzrNZ2Yz_0c-yyd4gfxjqeQFDknDH1ThAVIIhENFomwmcbXchQMosYz1x24ci94n3Cx6CuZlKOpxNmXGRPT7w",
  "token_type":"DPoP",
  "expires_in":3600
}

Step 10: Discovery with ABC Banking's token

ABC Banking can now use this token to discover resources it has access to, and retrieve the actual resources. These are the same requests previously used.

Request

GET /resources HTTP/1.1
Host: storage.sandbox-use.id
Accept: */*
Authorization: DPoP eyJhbGciOiJFUzI1NiIsImtpZCI6ImQ3NGMxZGZlLTFhNDEtNGEyMC1hNDJkLWZmYzBmMmJkZGE5OCIsInR5cCI6Imp3dCJ9.eyJzdWIiOiJodHRwczovL3dlYmlkLnNhbmRib3gtYWJjLWJhbmtpbmcuY29tLyIsInN1Yl93ZWJpZCI6Imh0dHBzOi8vd2ViaWQuc2FuZGJveC1hYmMtYmFua2luZy5jb20vIiwid2ViaWQiOiJodHRwczovL3dlYmlkLnNhbmRib3gtYWJjLWJhbmtpbmcuY29tLyIsImF1ZCI6WyJodHRwczovL3dlYmlkLnNhbmRib3gtYWJjLWJhbmtpbmcuY29tLyIsInNvbGlkIl0sImlzcyI6Imh0dHBzOi8vaWRwLnNhbmRib3gtdXNlLmlkLyIsImF6cCI6Imh0dHBzOi8vd2ViaWQuc2FuZGJveC1hYmMtYmFua2luZy5jb20vIiwiYXpwX3dlYmlkIjoiaHR0cHM6Ly93ZWJpZC5zYW5kYm94LWFiYy1iYW5raW5nLmNvbS8iLCJpYXQiOjE3MDcyOTk3MTMsImV4cCI6MTcwNzMwMzMxMywiY25mIjp7ImprdCI6ImJ0SUVlM21GbmpERW82VVpxeFh4RHhSUHhTdWpIZWlUQjV5U25sMVpUT28ifX0.0wzrNZ2Yz_0c-yyd4gfxjqeQFDknDH1ThAVIIhENFomwmcbXchQMosYz1x24ci94n3Cx6CuZlKOpxNmXGRPT7w
DPoP: eyJhbGciOiJFUzI1NiIsImp3ayI6eyJhbGciOiJFUzI1NiIsImNydiI6IlAtMjU2Iiwia3R5IjoiRUMiLCJ4IjoiTm1WNDZQQ3NKd0E5cXB1bjF5S29yTHR6MWh2TjJTZko3eHRTYlJOUlAwdyIsInkiOiIzTDI3TjRONFd3LVBBaVd6VXZqWjZRcW96b2RSRGxSYkREREFTbTVwZ1dvIn0sInR5cCI6ImRwb3Arand0In0.eyJhdGgiOiJmQWE5bXU5MktFVXQwdTJBZFY2aVVKb0I5SXQ4MjY1TFEzeDRHTzBzQmlnIiwiaHRtIjoiR0VUIiwiaHR1IjoiaHR0cHM6Ly9zdG9yYWdlLnNhbmRib3gtdXNlLmlkL3Jlc291cmNlcyIsImlhdCI6MTcwNzI5OTcxMywianRpIjoiZDdkNDFiY2UtYjk5OS00Y2Q0LWFkMTktOTEwMjZlMTM3YTY1In0.MbBZqaNu4aB7YZULGYEuwp_KXaNR8BNMfezkLvfMLOdHfaciXsQLboSEAM68aoyI1K3VZ67zsByPNL4G_N7jQw
X-Correlation-ID: 5ed0272b-615a-41ca-ac83-6aa6965d02a1

Response

HTTP/1.1 200 OK
Content-Type: application/json
last-modified: 2024-02-07T09:55:14.362Z
X-Request-ID: f9918e84-c52b-42ab-817d-59cd7c04f1c8
X-Correlation-ID: 5ed0272b-615a-41ca-ac83-6aa6965d02a1

{
  "subject_type_combinations":[
    {
      "subject":"https://sandbox-use.id/john-5ed0272b",
      "type":"https://test-types.com/address",
      "allowed_access_for_combination":["read","write","create"],
      "resources":[
        {
          "uri":"https://storage.sandbox-use.id/c110f9b2-832f-4a43-a418-f5cfc21c01ad",
          "content_type":"text/plain",
          "last_modified":"2024-02-07 09:54:01.445512+00",
          "last_modified_by":"sub=\"https://sandbox-use.id/john-5ed0272b\", azp=\"https://webid.sandbox-my-move.app/\", iss=\"https://idp.sandbox-use.id/\"",
          "created_at":"2024-02-07 09:54:01.445512+00",
          "created_by":"sub=\"https://sandbox-use.id/john-5ed0272b\", azp=\"https://webid.sandbox-my-move.app/\", iss=\"https://idp.sandbox-use.id/\"",
          "tags":"",
          "allowed_access_for_resource":["read","write"]
        }
      ]
    }
  ]
}