|
| 1 | +# Detailed Application Authentication |
| 2 | + |
| 3 | +This document outlines, in detail, the login and request process for an application using WebId-OIDC. In general, our user, *Alice* will be using a thrid-party application at `https://www.decentphotos.example` to access data on both her pod at `https://alice.example` and her friend, Bob's pod at `https://bob.example`. |
| 4 | + |
| 5 | +## Actors |
| 6 | + |
| 7 | +In this example a multitude of actors are at play: |
| 8 | + |
| 9 | +**Alice** - Alice will be providing consent for decentphotos to use her pod. Let's assume that Alice is using a standard web browser. |
| 10 | + |
| 11 | +**Bob's Pod (RS)** - We will be trying to access photos Bob's Pod, known in the OIDC world as a Resource Server (RS). Bob is a friend of Alice. For this use case, let's assume that Bob has previously indicated via access control that Alice may access his photo using any app. You can read more about access control [here](https://github.com/solid/web-access-control-spec#referring-to-origins-ie-web-apps). For this example, bob's pod is at `bob.solid.example`. |
| 12 | + |
| 13 | +**Alice's OP** - Alice's OpenID Provider (OP), also known as an IDP (Identity Provider), is the service responsible for authorizing our thrid-party app by providing it with the tokens necessary to gain access to any pod. In this demo, alice's OP is at `secureauth.example`. |
| 14 | + |
| 15 | +**Alice's Pod (RS)** - Alice's Pod is hosted at `alice.coolpod.example`, giving Alice the webId of `https://alice.coolpod.example/profile/card#me`. |
| 16 | + |
| 17 | +**Decent Photos (RP)** - decentphotos is a third party photo viewing application hosted at `https://www.decentphotos.example`. This app allows you to view your photos as well as your friend's photos. It will also perform cron jobs on the photos to detect faces. In the OIDC world this is known as the Relying Party (RP). |
| 18 | + |
| 19 | +## Application Flow |
| 20 | + |
| 21 | + |
| 22 | + |
| 23 | +### Authorization |
| 24 | + |
| 25 | +Before any requests can be made, Alice must log in: |
| 26 | + |
| 27 | +#### 1. Alice naviages to www.decentphotos.example |
| 28 | + |
| 29 | +Alice has heard of a great new site that allows her to view her friend's photos and tag faces. She navigates to `www.decentphotos.example` via he web browser which returns and html page. This page contains JavaScript that will help with the authorization process. |
| 30 | + |
| 31 | +#### 2. Alice clicks the "Connect" button |
| 32 | + |
| 33 | +Before decentphotos can start displaying images, Alice needs to start the process of providing consent. To do so, she must either provider her webId (`https://alice.coolpod.example/profile/card#me`) or the service the url of her OP (`https://secureauth.example`) |
| 34 | + |
| 35 | +While it is not the case with Alice, a user's Pod and OP can be hosted at the same domain. For example, Bob's pod could be `bob.solid.example` with a webId of `https://bob.solid.example/profile/card#me`, but his OP is at `https://solid.example`. |
| 36 | + |
| 37 | +#### 3. Request OP Configuration |
| 38 | + |
| 39 | +Now that Alice has indicated either her webId or her OP's url, the RP must make a request to retrieve the OP's configuration. |
| 40 | + |
| 41 | +If Alice entered her webId the request would be her webId's origin plus a path for the OIDC configuration: |
| 42 | +```bash |
| 43 | +GET https://alice.coolpod.example/.well-known/openid-configuration |
| 44 | +``` |
| 45 | + |
| 46 | +If Alice entered her OP's url, the RP would simply append the OIDC configuration to the end. |
| 47 | + |
| 48 | +``` |
| 49 | +GET https://secureauth.example/.well-known/openid-configuration |
| 50 | +``` |
| 51 | + |
| 52 | +#### 4. Returns OP Configuration |
| 53 | + |
| 54 | +Regardless of the what Alice entered, the response body should be the same. The [openid-configuration](https://auth0.com/docs/protocols/oidc/openid-connect-discovery) describes everything the client will need to know to authorize with Alice's specific OP. |
| 55 | + |
| 56 | +Response Body: |
| 57 | +```json |
| 58 | +{ |
| 59 | + "issuer":"https://secureauth.example", |
| 60 | + "authorization_endpoint":"https://secureauth.example/authorize", |
| 61 | + "token_endpoint":"https://secureauth.example/token", |
| 62 | + "userinfo_endpoint":"https://secureauth.example/userinfo", |
| 63 | + "jwks_uri":"https://secureauth.example/jwks", |
| 64 | + "registration_endpoint":"https://secureauth.example/register", |
| 65 | + "response_types_supported":[ |
| 66 | + "code", |
| 67 | + "code token", |
| 68 | + "code id_token", |
| 69 | + "id_token", |
| 70 | + "id_token token", |
| 71 | + "code id_token token", |
| 72 | + "none" |
| 73 | + ], |
| 74 | + "response_modes_supported":[ |
| 75 | + "query", |
| 76 | + "fragment" |
| 77 | + ], |
| 78 | + "grant_types_supported":[ |
| 79 | + "authorization_code", |
| 80 | + "implicit", |
| 81 | + "refresh_token", |
| 82 | + "client_credentials" |
| 83 | + ], |
| 84 | + "subject_types_supported":[ |
| 85 | + "public" |
| 86 | + ], |
| 87 | + "id_token_signing_alg_values_supported":[ |
| 88 | + "RS256", |
| 89 | + "RS384", |
| 90 | + "RS512", |
| 91 | + "none" |
| 92 | + ], |
| 93 | + "token_endpoint_auth_methods_supported":[ |
| 94 | + "client_secret_basic" |
| 95 | + ], |
| 96 | + "token_endpoint_auth_signing_alg_values_supported":[ |
| 97 | + "RS256" |
| 98 | + ], |
| 99 | + "display_values_supported":[ |
| 100 | + |
| 101 | + ], |
| 102 | + "claim_types_supported":[ |
| 103 | + "normal" |
| 104 | + ], |
| 105 | + "claims_supported":[ |
| 106 | + |
| 107 | + ], |
| 108 | + "claims_parameter_supported":false, |
| 109 | + "request_parameter_supported":true, |
| 110 | + "request_uri_parameter_supported":false, |
| 111 | + "require_request_uri_registration":false, |
| 112 | + "check_session_iframe":"https://secureauth.example/session", |
| 113 | + "end_session_endpoint":"https://secureauth.example/logout" |
| 114 | +} |
| 115 | +``` |
| 116 | + |
| 117 | +*RECOMMENDATION: It is recommended that this configuration is saved to local storage so that it does not need to be retrieved every time the RP needs to make a request to the OP* |
| 118 | + |
| 119 | +Currently in local storage: |
| 120 | +``` |
| 121 | +OPENID_CONFIGURATION |
| 122 | +``` |
| 123 | + |
| 124 | + |
| 125 | +#### 5. Generates a Private/Public key pair |
| 126 | + |
| 127 | +WebId-OIDC depends on [Proof of Posession (PoP) tokens](README.md#securing-tokens-for-multiple-resource-servers). PoP tokens ensure that third-party applications can send requests to any number of Pods, while ensuring that evil pods can't steal a user's token. |
| 128 | + |
| 129 | +The first step to generating a PoP token is generating a public and private key pair on the third-party RP. In our example, the private key is generated using `RSA256` and looks like: |
| 130 | + |
| 131 | +```json |
| 132 | +{ |
| 133 | + "alg":"RS256", |
| 134 | + "d":"UA69ET5dNnBYs1lbtNKEjozUXa3S5XYAjTkCcop1hxYAP4gM52iAkEQ0Jgs6CuVs74MxQM9vL__tMjH6KQ0sBvKTF_asnGYitftxBkABySwa-9bsDKtSrYO3F33Ctsiqp1WmZaT0eZi6rqjibm5ByYJRbyf9NDUI_farEaKoN6fTYGnIajNzzTKVm3PChbT4VyiOAADPGyJGOq5PX0oIbOHRG817NXYSZq8ZtDbm2_HoQwXjkSqz31d1UL8HKyghPWU4gBrkPyo_JWaoxbYlV5FGGXXUKBIvoaETf2-w5c7xNtpKJA7IS9IIKvZx-pVXP0nl5FjVC-I4ksQRY-FceQ", |
| 135 | + "dp":"Sa70sh4J65bbcDBEuHtx4wjxcsgDJjNHTbWc2B9HaZw49QF-sEI63SYezhF9DgR5oF_opEwjISAIv6bruHcb7Um7lxyxFV-iczR8NfO_wMhP9_EP8PyodBkX0YYQt2pBAizPwM_knKlRJOL3Dw5G2lnBDlzg6bWnA8oEM1UFvFk", |
| 136 | + "dq":"AmLtOjwwJPjt80yHGZc_OoFlKGDnm5m-5U8aS_kfIGbAFADYPuXacnMeOCVCthg8avCBgS68rC3hfNZeWkPnG2vXIiZqFLMPblQu6sjnlcvJS3peNQrJMs46ah6NJeUfLtniIcbveRu_CaosLwH_au5sPzpyStgELWiX5yV-bw", |
| 137 | + "e":"AQAB", |
| 138 | + "ext":true, |
| 139 | + "key_ops":[ |
| 140 | + "sign" |
| 141 | + ], |
| 142 | + "kty":"RSA", |
| 143 | + "n":"qpm6eDf1JvcOmhX1icr--xrD2_mx-SxpqIDArguhEcnhVsA6usKjttars25H8fpc_rtN6qvdhHCRHxIafLZ0PtWeZ9-CCKNgMnYViBW-F5j70RXSBfJI8zal2UrQQycbBhmPi6ATTLHaQyVfDM_rFpRmR99wh4h6QR2fBRAX2_J9_lPvFMofmjcYcfaOT9l3TIoghfW2ma3oLkIG4La0MSScfwzPFuAjqr7xO-sRGYB9OQDbOHJpuXOw0FYb8wxuMZjBRzFsudjaxlzlpr1eR6a5sTA8tAIs0f3j0oZQts358mMxp4oRPstUuExvrZcIQ7XODi3AvMwjbbqd5b1How", |
| 144 | + "p":"1nWyuXZpF7DcAXOxGzsod-itCTEI1hYv4CGXQ5daSvbasF0tqs9LuRzpTEGokLlzPINNjTPqo-lUElLIhdMkQsdbeYdIwI6FqmxjHx30V1yQqhhCsTT8RKRaHoN53EQsqb1r1RyEzGT6mhll7M0fMVxMuHnHTBPhrvMM5Yj6C_k", |
| 145 | + "q":"y6U0YyN1uFukE34iCh2bQDU4l1qpgHN_r1UzsSOzLKAo6vlqlLMdbzA5Fn5JHlDWZE7QkBSeTi845_O64MB1G6YvVIQTtGFgdom_p_DKO8pdFo3lIv9p3SRoHafBe2xb_bBT4wXAA71hzbydug95pJ0PN5CBw2-seZcalHcxf3s", |
| 146 | + "qi":"ZK7ILqxNETFmBOA3xiBZiT7pylUqUCjbnxc_Jv_1I8QyiCwO5WPjPuCeclZiPPVSf7IlA3mY9hB7HB72d33zIeL8Esd95iFj_rr4SiiQ9V5sS_vo1roAbfApTNx2uT7Jdcp5Td_362971qbvZbj8kvrrvgi7Dv7jffIhKx9ez4w" |
| 147 | +} |
| 148 | +``` |
| 149 | +From now on we will refer to this as `RP_PRIVATE_KEY`. The public key looks like: |
| 150 | + |
| 151 | +```json |
| 152 | +{ |
| 153 | + "jwk":{ |
| 154 | + "alg":"RS256", |
| 155 | + "e":"AQAB", |
| 156 | + "ext":true, |
| 157 | + "key_ops":[ |
| 158 | + "verify" |
| 159 | + ], |
| 160 | + "kty":"RSA", |
| 161 | + "n":"mVnn-HwEQi5mZR3Z0Wc7TBrJouOb7acKiseVSkjrj4mWCSEw21VTGNfovzUS71WYKoxFAd8zfkI9-lsAn3tkL8ppcMuI3F8KxsO86nNHSKrxZIlk-bP7RDFfpI9KWyifulKdipEmRit4iN-EFI2mK9KREscPWG083vqn4D81Xe4s0-gmRsBrVanwwu-mTwEKy8RFomV8CXOTcTNntdR3krluXZ38_uKBB1qg6_phBQwZ_sDMXWs8E90eCXhd_EQ6S8PGzCDPT2vg9wCB57ifAXt_8e4ZnqySmFPxegy7j3GcMuyhHzdpvv2fX5DvOxsgkjhsBzby9LD0bxStdBCSFQ" |
| 162 | + } |
| 163 | +} |
| 164 | +``` |
| 165 | +From now on we will refer to this as `RP_PUBLIC_KEY`. |
| 166 | + |
| 167 | +#### 6. Saves the Public/Private key to local storage |
| 168 | + |
| 169 | +The public/private key pair must be saved so that it can be referenced once the OP redirects back to the RP. It is recommended to save it to local storage, but that is not required. |
| 170 | + |
| 171 | +Currently in local storage: |
| 172 | +``` |
| 173 | +OPENID_CONFIGURATION |
| 174 | +RP_PRIVATE_KEY |
| 175 | +RP_PUBLIC_KEY |
| 176 | +``` |
| 177 | + |
| 178 | +#### 7. Requests OP JWKs |
| 179 | + |
| 180 | +Now that the RP's Public/Private keys are generated. The RP needs to be aware of the OP's public key. To do so a request should be made to the `jwks_uri` from the openid-configuration: |
| 181 | + |
| 182 | +``` |
| 183 | +GET https://secureauth.example/jwks |
| 184 | +``` |
| 185 | + |
| 186 | +#### 8. Returns OP JWKs |
| 187 | + |
| 188 | +The [JSON Web Key Set (JWKS)](https://auth0.com/docs/jwks) is returned. These will eventually be used to validate the signature of the token the OP will eventually issue. It is recommended that the result of this request should be saved to local storage. |
| 189 | + |
| 190 | +Response body: |
| 191 | +```json |
| 192 | +{ |
| 193 | + "keys":[ |
| 194 | + { |
| 195 | + "kty":"RSA", |
| 196 | + "kid":"xeOjes9u3AcU4LBzcanEM7pZLwSlxaN7U62ZzOBDQuw", |
| 197 | + "alg":"RS256", |
| 198 | + "key_ops":[ |
| 199 | + "verify" |
| 200 | + ], |
| 201 | + "e":"AQAB", |
| 202 | + "n":"oB2LgkiZZ5iLAz1d4ua7sVxdbzY2nIRkDtf4UE08mWsD6UYRzLR98_gMAfnKB8i9yPCQkxfA5w_SZq6Y7odG1qSwLHM2mb_O2GSvY9kaG00UpeeEJCR19c7Jkcmq3GXh4yujnm2TFQ6YAzYNgrXkHlusaFUApJaQN6zr4AvmR_vX_5i__Ku7nuU-GbaV75LSr8o0QANdYFF0ooz5DJvydPplF8mO9_oD7ceSNLWP1AXlFs5JH6MEhH02dELb4-zeLcVzhoqON60cABTpbYSf1lLbYZsVUQ3cYE9CxXaByY2YNuQgc0k29mSmUvwEs0hNA5xUcE3-y_qKpYKniErb9Q" |
| 203 | + } |
| 204 | + ] |
| 205 | +} |
| 206 | +``` |
| 207 | + |
| 208 | +Currently in local storage: |
| 209 | +``` |
| 210 | +OPENID_CONFIGURATION |
| 211 | +RP_PRIVATE_KEY |
| 212 | +RP_PUBLIC_KEY |
| 213 | +OP_JWKS |
| 214 | +``` |
| 215 | + |
| 216 | +#### 9. Sends Dynamic Registration Parameters |
| 217 | + |
| 218 | +Now we have everything we need to perform [dynamic client registration](https://openid.net/specs/openid-connect-registration-1_0.html). Because each Solid user could have a different OP, it is not feasible to expect the developers of RPs to manually register with every OP. Therefore, the client (RP) is registered dynamically. A request is sent to the OP's `registration_endpoint`: |
| 219 | + |
| 220 | +``` |
| 221 | +POST https://secureauth.example/register |
| 222 | +``` |
| 223 | +Data: |
| 224 | +```json |
| 225 | +{ |
| 226 | + grant_types: ["implicit"] |
| 227 | + issuer: "https://secureauth.example" |
| 228 | + redirect_uris: ["https://www.decentphotos.example/"] |
| 229 | + response_types: ["id_token token"] |
| 230 | + scope: "openid profile" |
| 231 | +} |
| 232 | +``` |
| 233 | + |
| 234 | +Each of these communicates something about the new client to the OP: |
| 235 | + - `grant_types`: A list of [OIDC grant types](http://docs.identityserver.io/en/latest/topics/grant_types.html) this client will use. `implicit` is great for web applications. |
| 236 | + - `issuer`: Alice's OP |
| 237 | + - `redirect_uris`: Redirect uris provided at the client registration stage state which redirect uris are valid during the authorization stage. |
| 238 | + - `response_types`: A list of [OIDC response types](https://openid.net/specs/oauth-v2-multiple-response-types-1_0.html) the client can use. `id_token token` means that // TODO: Ask Dmitri about wrapping an id_token in a pop token. Should it not be the access token? |
| 239 | + - `scope`: OIDC uses scope as a way of defining what a client can have acces to. However, Solid has it's own access control system, so scope will always be `openid profile` |
| 240 | + |
| 241 | +#### 10: Saves Client Information |
| 242 | + |
| 243 | +The OP saves the client information to read later. |
| 244 | + |
| 245 | +#### 11: Returns successful registration |
| 246 | + |
| 247 | +The OP responds confirming the request, and with some new data. |
| 248 | + |
| 249 | +Response Body: |
| 250 | +```json |
| 251 | +{ |
| 252 | + "client_id":"7243fd594bdcf9c71a9b902274afaa30", |
| 253 | + "redirect_uris":[ |
| 254 | + "https://chat.o.team/" |
| 255 | + ], |
| 256 | + "response_types":[ |
| 257 | + "id_token token" |
| 258 | + ], |
| 259 | + "grant_types":[ |
| 260 | + "implicit" |
| 261 | + ], |
| 262 | + "application_type":"web", |
| 263 | + "id_token_signed_response_alg":"RS256", |
| 264 | + "token_endpoint_auth_method":"client_secret_basic", |
| 265 | + "frontchannel_logout_session_required":false, |
| 266 | + "registration_access_token":"eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJodHRwczovL3NvbGlkLmNvbW11bml0eSIsInN1YiI6IjcyNDNmZDU5NGJkY2Y5YzcxYTliOTAyMjc0YWZhYTMwIiwiYXVkIjoiNzI0M2ZkNTk0YmRjZjljNzFhOWI5MDIyNzRhZmFhMzAifQ.gypiiq3K5_oRpEN7e1KaobI5CwWdrr7ZpwhdjUMneSEOEUVOwE0qeWlzu44j24eOIGeX8PzTc-f5ca0cRk22HSSRZIcAVo-GmGlZ4oAA5uGOuiEdncaF3ZKDv-0q1WXiyUFD_2hiiUrQwLtt-iSo3ZQOlCMP3opC6A73-b6UpOJTc9Q-V-sNTtOZ5IHpgOSkQZbgSuQr_vTGRLjoHl_v_8-AarjUIytbf_6h9iEFuPOyqugMUFALeNPBHSN8CTgJI3jcvx4HZtM9ByHNGGPjZv4TJrXy1sJIHokJkwdg1Pv_3mkUKVwtO4zxKAOu5MlspaZo6c-Oku__9S2mnu88xQ", |
| 267 | + "registration_client_uri":"https://solid.community/register/7243fd594bdcf9c71a9b902274afaa30", |
| 268 | + "client_id_issued_at":1557964995 |
| 269 | +} |
| 270 | +``` |
| 271 | + |
| 272 | +The main one to keep note of is the `client_id` which will be used to tie subsequent requests back to this registered client. |
| 273 | + |
| 274 | +This information should be saved to local storage. |
| 275 | + |
| 276 | +Currently in local storage: |
| 277 | +``` |
| 278 | +OPENID_CONFIGURATION |
| 279 | +RP_PRIVATE_KEY |
| 280 | +RP_PUBLIC_KEY |
| 281 | +OP_JWKS |
| 282 | +CLIENT_REGISTRATION_RESPONSE |
| 283 | +``` |
| 284 | + |
| 285 | +#### 12. Authorization Request |
| 286 | + |
| 287 | +// TODO complete explanation |
| 288 | + |
| 289 | +#### |
0 commit comments