|
| 1 | +# Getting started |
| 2 | + |
| 3 | +This tutorial uses step-by-step examples to explain |
| 4 | +how the different pieces in this repository work together |
| 5 | +to combine a User Managed Acces (UMA) authorization server with a Solid resource server, |
| 6 | +and which additions we have made to make this work. |
| 7 | +The main goal is to have a grasp on how to use this project, |
| 8 | +which means some concepts of the several protocols involved here |
| 9 | +are simplified or omitted. |
| 10 | + |
| 11 | +For the full details, we refer to the official documentation: |
| 12 | +* UMA: https://docs.kantarainitiative.org/uma/wg/rec-oauth-uma-grant-2.0.html |
| 13 | +* Federated UMA: https://docs.kantarainitiative.org/uma/wg/rec-oauth-uma-federated-authz-2.0.html |
| 14 | +* Solid: https://solidproject.org/TR/ |
| 15 | +* A4DS: https://spec.knows.idlab.ugent.be/A4DS/L1/latest/ |
| 16 | + * This covers changes and choices made in this repository |
| 17 | + |
| 18 | +It is recommended to go through the |
| 19 | +[Community Solid Server (CSS) tutorial](https://github.com/CommunitySolidServer/tutorials/blob/main/getting-started.md), |
| 20 | +as that covers the basics of Solid and the CSS, |
| 21 | +which is used as a basis for this repository and this guide. |
| 22 | + |
| 23 | +Note that this repository, and how the protocols are implemented, is still changing, |
| 24 | +so some information might change depending on which version and branch you're using. |
| 25 | + |
| 26 | +## Index |
| 27 | + |
| 28 | +- [Getting started](#getting-started) |
| 29 | + * [Starting the server](#starting-the-server) |
| 30 | + * [Authenticating the Resource Server](#authenticating-the-resource-server) |
| 31 | + * [Resource registration](#resource-registration) |
| 32 | + + [About identifiers](#about-identifiers) |
| 33 | + * [Resource access](#resource-access) |
| 34 | + + [Informing the UMA Authorization Server](#informing-the-uma-authorization-server) |
| 35 | + + [Generating a ticket](#generating-a-ticket) |
| 36 | + - [Publicly accessible resources](#publicly-accessible-resources) |
| 37 | + + [Exchange ticket](#exchange-ticket) |
| 38 | + - [Claim security](#claim-security) |
| 39 | + + [Generate token](#generate-token) |
| 40 | + + [Use token](#use-token) |
| 41 | + * [Policies](#policies) |
| 42 | + * [Adding or changing policies](#adding-or-changing-policies) |
| 43 | + |
| 44 | +<small><i><a href='http://ecotrust-canada.github.io/markdown-toc/'>Table of contents generated with markdown-toc</a></i></small> |
| 45 | + |
| 46 | +## Starting the server |
| 47 | + |
| 48 | +This repository contains several startup and test scripts, |
| 49 | +as described in the [README](../README.md). |
| 50 | +In this guide, we describe more in-depth what happens in some of these scripts. |
| 51 | + |
| 52 | +To begin, run the `yarn start` script, |
| 53 | +which sets up the necessary servers. |
| 54 | +Specifically, this starts a UMA Authorization Server (AS) at `http://localhost:4000`, |
| 55 | +and a Solid Resource Server (RS) at `http://localhost:3000`. |
| 56 | +It also initializes several resources in-memory, |
| 57 | +using [pod seeding](https://communitysolidserver.github.io/CommunitySolidServer/latest/usage/seeding-pods/), |
| 58 | +to make it easier to get started. |
| 59 | + |
| 60 | +You can see the AS working by going to <http://localhost:4000/uma/.well-known/uma2-configuration>. |
| 61 | +This page contains all the relevant APIs of the UMA server, |
| 62 | +which are used in the next steps. |
| 63 | + |
| 64 | +## Authenticating the Resource Server |
| 65 | + |
| 66 | +Throughout this guide, there are several instances where the RS has to send an HTTP request to the AS. |
| 67 | +The AS needs some way to verify if the request comes from the RS. |
| 68 | +The current implementation makes use of [HTTP signatures](https://datatracker.ietf.org/doc/html/rfc9421). |
| 69 | +To enable this, the RS needs to expose a [JSON Web Key](https://datatracker.ietf.org/doc/html/rfc7517). |
| 70 | +This key can be found at the `jwks_uri` API of OpenID configuration of the RS, |
| 71 | +seen at <http://localhost:3000/.well-known/openid-configuration>. |
| 72 | +The RS uses that same key to sign its messages as described in the RFC, |
| 73 | +using the [http-message-signatures](https://www.npmjs.com/package/http-message-signatures) library. |
| 74 | +This is done for every HTTP request the RS sends to the AS in the following sections. |
| 75 | + |
| 76 | +## Resource registration |
| 77 | + |
| 78 | +The Federated UMA specification requires that the RS registers every resource at the AS. |
| 79 | +This way the AS knows for which resources it needs to manage the access. |
| 80 | +As several resources are created immediately due to the pod seeding, |
| 81 | +these all need to be registered at the AS. |
| 82 | +The RS does this by POSTing a request to the `resource_registration_endpoint` API with the following body: |
| 83 | +```json |
| 84 | +{ |
| 85 | + "resource_scopes": [ |
| 86 | + "urn:example:css:modes:read", |
| 87 | + "urn:example:css:modes:append", |
| 88 | + "urn:example:css:modes:create", |
| 89 | + "urn:example:css:modes:delete", |
| 90 | + "urn:example:css:modes:write" |
| 91 | + ] |
| 92 | +} |
| 93 | +``` |
| 94 | + |
| 95 | +This tells the AS that it should register a new resource, |
| 96 | +and what its available scopes are. |
| 97 | +The above scopes are those currently supported by the server setup, |
| 98 | +and are mostly based on the similar scopes defined by |
| 99 | +the [WAC specification](https://solid.github.io/web-access-control-spec/). |
| 100 | +The `create` scope is different and indicates the client wants to create a new resource in the given container. |
| 101 | + |
| 102 | +When the AS receives this request, it mints a new identifier. |
| 103 | +This identifier is used to represent the resource on the AS side, |
| 104 | +in the relevant policies that determine access. |
| 105 | +The Solid identifier of the resource is irrelevant, |
| 106 | +and not even known by the AS. |
| 107 | +If the request is successful, |
| 108 | +the AS responds with a 201 status code. |
| 109 | +The location header contains the new identifier. |
| 110 | +The RS stores this identifier, linked to the Solid identifier, for future use. |
| 111 | + |
| 112 | +### About identifiers |
| 113 | + |
| 114 | +As mentioned above, the UMA identifier and Solid identifier are independent identifiers, |
| 115 | +with the UMA AS only knowing the former. |
| 116 | +This means that whoever writes the policies that determine access, |
| 117 | +need to be aware of the UMA identifiers of resources. |
| 118 | +Work is currently being done on having an API that provides all the necessary information, |
| 119 | +so users are informed of which resources correspond to which identifiers. |
| 120 | +As this is currently not clear to users yet, |
| 121 | +the "minted" identifier on the UMA server is the same as the Solid identifier. |
| 122 | +To inform the UMA server of what the Solid identifier is, |
| 123 | +the Solid RS needs to add a `name` field to the registration body described above, |
| 124 | +with the value being the Solid identifier. E.g.: |
| 125 | +```json |
| 126 | +{ |
| 127 | + "name": "http://localhost:3000/my/resource", |
| 128 | + "resource_scopes": [ |
| 129 | + "urn:example:css:modes:read", |
| 130 | + "urn:example:css:modes:append", |
| 131 | + "urn:example:css:modes:create", |
| 132 | + "urn:example:css:modes:delete", |
| 133 | + "urn:example:css:modes:write" |
| 134 | + ] |
| 135 | +} |
| 136 | +``` |
| 137 | +In the future, this field will be used to describe the resource instead of using it as the actual identifier. |
| 138 | + |
| 139 | +## Resource access |
| 140 | + |
| 141 | +When trying to access a resource, |
| 142 | +several steps have to be taken by both the client and both servers. |
| 143 | +These are described in-depth in the relevant specifications. |
| 144 | +In this section, we go through all the steps of a single PUT request, |
| 145 | +targeting `http://localhost:3000/alice/private/resource.txt`. |
| 146 | +This same example can be seen in [scripts/test-private.ts](../scripts/test-private.ts). |
| 147 | + |
| 148 | +### Informing the UMA Authorization Server |
| 149 | + |
| 150 | +The first step of a request is the same as it is for a Solid server with a standard authorization server: |
| 151 | +send the PUT request to the Solid RS. |
| 152 | +As usual, the RS first determines which scopes are necessary. |
| 153 | +As `http://localhost:3000/alice/private/resource.txt` does not exist yet, |
| 154 | +these scopes need to indicate that the client wants to create a new resource. |
| 155 | +When using UMA, scopes need to be determined on registered resources. |
| 156 | +This means that it is not possible to require scopes on resources that do not exist yet, |
| 157 | +as is possible with WAC. |
| 158 | +To support this requirement, when a new resource needs to be created, |
| 159 | +`create` permissions will be required on the first existing parent container. |
| 160 | +Since `http://localhost:3000/alice/private/` also does not exist, |
| 161 | +this request requires `create` permissions on `http://localhost:3000/alice/`. |
| 162 | + |
| 163 | +Once the RS determines the scopes, it contacts the AS through the `permission_endpoint` API. |
| 164 | +It performs a `POST` request with a JSON body containing the UMA identifier of the resource |
| 165 | +and the requested scopes, which looks as follows: |
| 166 | +```json |
| 167 | +{ |
| 168 | + "resource_id": "12345", // Assume this is the UMA ID of http://localhost:3000/alice/ |
| 169 | + "resource_scopes": [ |
| 170 | + "urn:example:css:modes:create" |
| 171 | + ] |
| 172 | +} |
| 173 | +``` |
| 174 | + |
| 175 | +### Generating a ticket |
| 176 | + |
| 177 | +The first thing the AS has to do when receiving any HTTP request is to validate the signature, as discussed above. |
| 178 | +Afterward, it creates a ticket identifier, links it with the request body, |
| 179 | +and responds to the RS request with a 201 status code. |
| 180 | +The location header of the response contains the ticket identifier. |
| 181 | +The RS then responds to the client, which is still waiting for a response, |
| 182 | +with a 401 status code. |
| 183 | +To inform the client on how to acquire access, |
| 184 | +the 401 response has a `WWW-Authenticate` header |
| 185 | +with value `UMA realm="solid", as_uri="http://localhost:4000/uma/", ticket="TICKET_ID"`. |
| 186 | +The client then parses this header to know where the AS is, |
| 187 | +and what the ticket identifier is that it needs to present there. |
| 188 | + |
| 189 | +#### Publicly accessible resources |
| 190 | + |
| 191 | +UMA requires the above process for every resource access. |
| 192 | +This makes it impossible to have public resources that can be accessed with, for example, a simple GET request. |
| 193 | +To still allow for such situations, |
| 194 | +the AS will return a 200 response, instead of a 201, |
| 195 | +if it determines no claims are required to perform the request. |
| 196 | +In that case, the RS will execute the client's request immediately, instead of returning a 401 with a ticket. |
| 197 | + |
| 198 | +### Exchange ticket |
| 199 | + |
| 200 | +To receive access, the client has to exchange the ticket for a token at the AS. |
| 201 | +This is done through the `token_endpoint` API. |
| 202 | +Besides the ticket, the client has to include the necessary claims to identify itself. |
| 203 | +The only claim currently supported by the AS, is the WebID, |
| 204 | +which for this example is `https://woslabbi.pod.knows.idlab.ugent.be/profile/card#me`. |
| 205 | +To make the request, the client performs a POST with the following JSON body: |
| 206 | +```json |
| 207 | +{ |
| 208 | + "grant_type": "urn:ietf:params:oauth:grant-type:uma-ticket", |
| 209 | + "ticket": "TICKET_ID", |
| 210 | + "claim_token": "https%3A%2F%2Fwoslabbi.pod.knows.idlab.ugent.be%2Fprofile%2Fcard%23me", |
| 211 | + "claim_token_format": "urn:solidlab:uma:claims:formats:webid" |
| 212 | +} |
| 213 | +``` |
| 214 | +The `claim_token_format` explains to the AS how the `claim_token` should be interpreted. |
| 215 | +In this case, this is a custom format designed for this server. |
| 216 | + |
| 217 | +#### Claim security |
| 218 | + |
| 219 | +In the above body, the claim token format is a string representing a WebID. |
| 220 | +No actual authentication or verification takes place here, |
| 221 | +meaning anyone can insert any WebID they want. |
| 222 | +This allows for easy testing and examples, |
| 223 | +but will be changed to an actual safe method, such as an OIDC ID token, in the future. |
| 224 | + |
| 225 | +### Generate token |
| 226 | + |
| 227 | +Once the AS receives a token request, it has to match the ticket ID |
| 228 | +with the scopes it stored internally in a previous step. |
| 229 | +Based on the stored policies, it then determines if the provided claims are sufficient to allow the request. |
| 230 | +How these policies work will be covered later on. |
| 231 | +If successful, the server will return a 200 response with a JSON body containing, among others, |
| 232 | +an `access_token` field containing the access token, and a `token_type` field describing the token type. |
| 233 | +If the claims are insufficient, a 403 response will be given instead. |
| 234 | + |
| 235 | +### Use token |
| 236 | + |
| 237 | +When receiving the access token, the client can perform the same request as it did in the first step, |
| 238 | +but now include an `Authorization` header with value `TOKEN_TYPE ACCESS_TOKEN`, |
| 239 | +based on the response values in the previous step. |
| 240 | +When receiving this, the RS validates the token with the AS, |
| 241 | +similarly how this is done with a standard Solid server with OIDC. |
| 242 | +If the token is valid, |
| 243 | +it then performs the request the client wanted. |
| 244 | + |
| 245 | +## Policies |
| 246 | + |
| 247 | +To determine the allowed scopes on a resource, |
| 248 | +the AS makes use of ODRL policies, |
| 249 | +for which we refer to the [specification](https://www.w3.org/TR/odrl-model/) for the full details. |
| 250 | +For our purposes, the AS does not use everything from the ODRL specification yet, such as refinements and duties, |
| 251 | +but only the core building blocks. |
| 252 | +Below is an example of the policy that allowed the example above to succeed: |
| 253 | +```ttl |
| 254 | +@prefix ex: <http://example.org/1707120963224#> . |
| 255 | +@prefix odrl: <http://www.w3.org/ns/odrl/2/> . |
| 256 | +
|
| 257 | +ex:usagePolicy a odrl:Agreement ; |
| 258 | + odrl:permission ex:permission . |
| 259 | +ex:permission a odrl:Permission ; |
| 260 | + odrl:action odrl:create ; |
| 261 | + odrl:target <alice/private/> , <alice/private/resource.txt> ; |
| 262 | + odrl:assignee <https://woslabbi.pod.knows.idlab.ugent.be/profile/card#me> . |
| 263 | +``` |
| 264 | +This policy says that the above WebID has access to the `create` scope |
| 265 | +on `<alice/private/>` and `<alice/private/resource.txt>`. |
| 266 | +The server is configured so the base URL of all policy documents is the URL of the RS, `http://localhost:3000/`, |
| 267 | +to make sure the identifiers match the Solid resource identifiers as discussed [above](#about-identifiers). |
| 268 | + |
| 269 | +## Adding or changing policies |
| 270 | + |
| 271 | +When starting the server with `yarn start`, |
| 272 | +the server is configured to load all policies from a specified folder. |
| 273 | +In this case, this is [packages/uma/config/rules/policy](../packages/uma/config/rules/policy). |
| 274 | +This is done by setting the Components.js variable `urn:uma:variables:policyBaseIRI` to the necessary folder, |
| 275 | +as can be seen in the start script at [packages/uma/bin/main.js](../packages/uma/bin/main.js). |
| 276 | +With this setup, |
| 277 | +there is no way to change the policies while the server is running. |
| 278 | +The only way is to change the folder and restart the server. |
| 279 | + |
| 280 | +An alternative setup is used with the `yarn start:demo` script. |
| 281 | +There the server is configured to read all policies from a specific Solid container, |
| 282 | +set by the `urn:uma:variables:policyContainer` variable. |
| 283 | +This way, it is possible to modify the policies at run-time by changing the contents of that container. |
| 284 | +In this case, it is important to make sure UMA is not used to handle the access of that container, |
| 285 | +as that would prevent the UMA server from readings its contents to determine the policies. |
| 286 | + |
| 287 | +Both of these options have their issues, |
| 288 | +so work is being done on having a more secure and usable solution. |
0 commit comments