|
| 1 | +<script lang="ts"> |
| 2 | + import SectionTitle from "$lib/components/SectionTitle.svelte" |
| 3 | + import Code from "$lib/components/Code.svelte" |
| 4 | + import json from "svelte-highlight/languages/json" |
| 5 | + import xml from "svelte-highlight/languages/xml" |
| 6 | + import Breadcrumbs from "$lib/components/Breadcrumbs.svelte" |
| 7 | + import Title from "$lib/components/Title.svelte" |
| 8 | + import TableOfContents from "$lib/components/TableOfContents.svelte" |
| 9 | +
|
| 10 | + import type { PageData } from "./$types" |
| 11 | + import GuideImage from "$lib/components/GuideImage.svelte" |
| 12 | + import A from "$lib/components/A.svelte" |
| 13 | + import { GUIDES } from "../urls" |
| 14 | +
|
| 15 | + export let data: PageData |
| 16 | + const images = data.images |
| 17 | +</script> |
| 18 | + |
| 19 | +<TableOfContents> |
| 20 | + <Title title={data.guide.title} /> |
| 21 | + |
| 22 | + <Breadcrumbs path={data.path} /> |
| 23 | + |
| 24 | + <SectionTitle title="Introduction" /> |
| 25 | + |
| 26 | + <p> |
| 27 | + Data sources that are set to use the <em>Dataspace verified API tokens</em> access control mode |
| 28 | + will in each request coming from the product gateway on the dataspace receive an API token in |
| 29 | + the <em>X-API-Key</em> HTTP Header. |
| 30 | + </p> |
| 31 | + |
| 32 | + <p>There are two main ways a data source can verify this API token:</p> |
| 33 | + |
| 34 | + <ol> |
| 35 | + <li> |
| 36 | + <A href="#validate-token-as-a-jwt-signed-by-the-dataspace"> |
| 37 | + Validate the token as a JWT signed by the dataspace |
| 38 | + </A> |
| 39 | + </li> |
| 40 | + <li> |
| 41 | + <A href="#token-verification-api"> |
| 42 | + Use the token verification endpoint on the product gateway |
| 43 | + </A> |
| 44 | + </li> |
| 45 | + </ol> |
| 46 | + |
| 47 | + <SectionTitle title="Validate token as a JWT signed by the dataspace" /> |
| 48 | + |
| 49 | + <p> |
| 50 | + The API token is actually a JWT token signed by the dataspace and can easily be verified using |
| 51 | + most common JWT libraries. |
| 52 | + </p> |
| 53 | + |
| 54 | + <p> |
| 55 | + As an example here’s a token that has been verified and decoded using the |
| 56 | + <A href="https://jwt.io">https://jwt.io</A> service: |
| 57 | + </p> |
| 58 | + |
| 59 | + <GuideImage img={images.JWT_IO_DECODED_TOKEN} /> |
| 60 | + |
| 61 | + <p> |
| 62 | + To verify the token you need to find the public keys of the dataspace. They can be found by |
| 63 | + checking the |
| 64 | + <em>/.well-known/dataspace/dataspace-configuration.json</em> on the base domain of the dataspace |
| 65 | + and locating the |
| 66 | + <em>jwks_url</em>. For example the |
| 67 | + <A href="https://ioxio.io/.well-known/dataspace/dataspace-configuration.json"> |
| 68 | + dataspace configuration on ioxio.io |
| 69 | + </A> points to |
| 70 | + <A href="https://ioxio.io/.well-known/jwks.json">https://ioxio.io/.well-known/jwks.json</A>. |
| 71 | + </p> |
| 72 | + |
| 73 | + <p>Things to ensure:</p> |
| 74 | + |
| 75 | + <ul> |
| 76 | + <li> |
| 77 | + The <em>aud</em> must match the DSI (Data Source Identifier) of your source. This is critical to |
| 78 | + verify! If you don’t verify this and someone else figures out the address at which your data source |
| 79 | + lives, they could publish it as their own data source on the same dataspace and grant access to |
| 80 | + it to anyone they want and get JWTs that are in all other aspects valid. |
| 81 | + </li> |
| 82 | + <li>The token is signed with one of the keys of the dataspace.</li> |
| 83 | + <li> |
| 84 | + The token hasn’t expired - make sure the <em>exp</em> is in the future and the <em>iat</em> is |
| 85 | + in the past (potentially allowing some reasonable leeway to account for clock differences, e.g. |
| 86 | + 5 minutes). |
| 87 | + </li> |
| 88 | + <li> |
| 89 | + The issuer (<em>iss</em>) is the base URL of the dataspace, for example |
| 90 | + <em>https://ioxio.io</em>, on which you’ve published the data source. |
| 91 | + </li> |
| 92 | + </ul> |
| 93 | + |
| 94 | + <p> |
| 95 | + In case you want to do some more fine-grained access control to what data who has access to, you |
| 96 | + can use the <em>sub</em> to identify the group access was granted to. |
| 97 | + </p> |
| 98 | + |
| 99 | + <p> |
| 100 | + An example implementation can be found on |
| 101 | + <A |
| 102 | + href="https://github.com/ioxio-dataspace/example-productizer/blob/3dd2435183ed5cbbd30c99a473a8d9c2ccf6b7c7/app/api_tokens.py#L221-L278" |
| 103 | + > |
| 104 | + https://github.com/ioxio-dataspace/example-productizer/blob/3dd2435183ed5cbbd30c99a473a8d9c2ccf6b7c7/app/api_tokens.py#L221-L278</A |
| 105 | + >. |
| 106 | + </p> |
| 107 | + |
| 108 | + <SectionTitle title="Validating the token as a JWT in Azure API Management" /> |
| 109 | + |
| 110 | + <p> |
| 111 | + In Azure API Management you can use the <em>Validate JWT</em> inbound processing policy to |
| 112 | + validate the API Token from the <em>X-API-Key</em> header. |
| 113 | + </p> |
| 114 | + |
| 115 | + <p> |
| 116 | + You should point the <em>Open ID URLs</em> to a URL of the form |
| 117 | + <em>https://<dataspace-domain>/.well-known/openid-configuration</em>. So for example for |
| 118 | + <A href="https://IOXIO.io">IOXIO.io</A> |
| 119 | + you would set it to <em>https://ioxio.io/.well-known/openid-configuration</em>. |
| 120 | + </p> |
| 121 | + |
| 122 | + <p> |
| 123 | + The <em>Audiences</em> should be set to match the DSI (Data Source Identifier) of your source. This |
| 124 | + is critical! If you don’t set this and someone else figures out the address of your API Management |
| 125 | + endpoint they could publish it as their own data source on the same dataspace and grant access to |
| 126 | + it to anyone they want and get JWTs that are in all other aspects valid. |
| 127 | + </p> |
| 128 | + |
| 129 | + <p> |
| 130 | + The default values are to require an expiration time and to require the tokens to be signed, but |
| 131 | + we still encourage to explicitly set them. |
| 132 | + </p> |
| 133 | + |
| 134 | + <p> |
| 135 | + You might also want to allow some clock skew, for example 5 minutes or 300 seconds should be |
| 136 | + more than enough. |
| 137 | + </p> |
| 138 | + |
| 139 | + <p> |
| 140 | + You might also like output the parsed token to a variable for further processing. The |
| 141 | + <em>sub</em> field of the token identifies the group the access was granted to, so you might want |
| 142 | + to use it to check what data you want to give which group access to. |
| 143 | + </p> |
| 144 | + |
| 145 | + <p>In the UI the configuration could look like this:</p> |
| 146 | + |
| 147 | + <GuideImage img={images.AZURE_API_MANAGEMENT_POLICY} /> |
| 148 | + |
| 149 | + <p>And in the code editor the configuration should look somewhat similar to this:</p> |
| 150 | + |
| 151 | + <Code lang={xml}> |
| 152 | + {` |
| 153 | +<validate-jwt |
| 154 | + header-name="X-API-Key" |
| 155 | + require-expiration-time="true" |
| 156 | + require-signed-tokens="true" |
| 157 | + clock-skew="300" |
| 158 | + output-token-variable-name="jwt"> |
| 159 | + <openid-config url="https://ioxio.io/.well-known/openid-configuration" /> |
| 160 | + <audiences> |
| 161 | + <audience>dpp://group:variant@ioxio.io/Meteorology/Weather_v0.1</audience> |
| 162 | + </audiences> |
| 163 | +</validate-jwt> |
| 164 | + `} |
| 165 | + </Code> |
| 166 | + |
| 167 | + <p> |
| 168 | + You might also find the documentation on |
| 169 | + <A href="https://learn.microsoft.com/en-us/azure/api-management/validate-jwt-policy"> |
| 170 | + validating JWTs in Azure API Management</A |
| 171 | + > useful. |
| 172 | + </p> |
| 173 | + |
| 174 | + <SectionTitle title="Token verification API" /> |
| 175 | + |
| 176 | + <p> |
| 177 | + The product gateway offers an endpoint at the path <em>/api/v1/api-token/verify</em>, for |
| 178 | + example on <em>https://gateway.ioxio.io/api/v1/api-token/verify</em>. It expects a |
| 179 | + <strong>POST</strong> request with a body of the form: |
| 180 | + </p> |
| 181 | + |
| 182 | + <Code lang={json}> |
| 183 | + {` |
| 184 | +{ |
| 185 | + "aud": "dpp://group:variant@ioxio.io/Meteorology/Weather_v0.1", |
| 186 | + "apiToken": "eyJ..." |
| 187 | +} |
| 188 | + `} |
| 189 | + </Code> |
| 190 | + |
| 191 | + <p> |
| 192 | + The <em>aud</em> needs to match the DSI (Data Source Identifier) of the source you are |
| 193 | + providing, the <em>apiToken</em> is the token from the request that you want to verify is valid for |
| 194 | + your data source. |
| 195 | + </p> |
| 196 | + |
| 197 | + <p> |
| 198 | + In case of a valid token you will get a 200 OK response with <em>"valid": true</em> and some additional |
| 199 | + details of the token, similar to this: |
| 200 | + </p> |
| 201 | + |
| 202 | + <Code lang={json}> |
| 203 | + {` |
| 204 | +{ |
| 205 | + "valid": true, |
| 206 | + "aud": "dpp://group:variant@ioxio.io/Meteorology/Weather_v0.1", |
| 207 | + "exp": 1733758438, |
| 208 | + "iat": 1733754838, |
| 209 | + "iss": "https://ioxio.io", |
| 210 | + "sub": "ioxio" |
| 211 | +} |
| 212 | + `} |
| 213 | + </Code> |
| 214 | + |
| 215 | + <p> |
| 216 | + In case the API token is not valid, you will get a 403 error response with |
| 217 | + <em>"success": false</em>, similar to this: |
| 218 | + </p> |
| 219 | + |
| 220 | + <Code lang={json}> |
| 221 | + {` |
| 222 | +{ |
| 223 | + "success": false, |
| 224 | + "message": "API token is not valid for this source or it has expired" |
| 225 | +} |
| 226 | + `} |
| 227 | + </Code> |
| 228 | + |
| 229 | + <p>Any other possible errors should also be treated as failures.</p> |
| 230 | + |
| 231 | + <SectionTitle title="Testing" /> |
| 232 | + |
| 233 | + <p> |
| 234 | + The <A href={GUIDES.BUILD_DATA_SOURCE.href}> |
| 235 | + {GUIDES.BUILD_DATA_SOURCE.title} |
| 236 | + </A> guide explains in the |
| 237 | + <A href="{GUIDES.BUILD_DATA_SOURCE.href}#test-your-data-source-using-swagger-ui"> |
| 238 | + Test your data source using Swagger UI |
| 239 | + </A> |
| 240 | + section how you can easily obtain an API token and use the Swagger UI to test your data source through |
| 241 | + the product gateway of the dataspace. |
| 242 | + </p> |
| 243 | + |
| 244 | + <GuideImage img={images.SWAGGER_UI_RESPONSE_CURL} /> |
| 245 | + |
| 246 | + <p> |
| 247 | + If you want to test the token validation works correctly also in your own implementation, you |
| 248 | + can simply copy the <em>curl</em> example that is shown there and replace the URL with the one |
| 249 | + for your own implementation (assuming you have <em>curl</em> installed and can run it in a terminal). |
| 250 | + </p> |
| 251 | + |
| 252 | + <p> |
| 253 | + You can equally well make the request against your actual deployed instance or against your own |
| 254 | + local development setup by changing the URL to something like |
| 255 | + <em>https://my-server.example.com/Meteorology/Weather_v0.1?source=my_group</em> or |
| 256 | + <em>http://localhost:8080/Meteorology/Weather_v0.1?source=my_group</em>. |
| 257 | + </p> |
| 258 | + |
| 259 | + <p> |
| 260 | + To test with invalid tokens you can let the token expire or you can make it invalid by removing |
| 261 | + the last character (the token consists of three parts, separated by dots <em>.</em>, the first |
| 262 | + one contains the header, the middle one the payload and the last part is the signature, so any |
| 263 | + change after the last dot will invalidate the signature, but keep the header and payload |
| 264 | + intact). |
| 265 | + </p> |
| 266 | + |
| 267 | + <p> |
| 268 | + You could also register a temporary dummy data source using another <em>variant</em> when |
| 269 | + creating it or create it using a different definition just to be able to generate API tokens for |
| 270 | + them. API tokens generated for those sources should not be valid due to the differences in the |
| 271 | + <em>aud</em> field compared to the real source. |
| 272 | + </p> |
| 273 | + |
| 274 | + <SectionTitle title="Granting access" /> |
| 275 | + |
| 276 | + <p> |
| 277 | + The <A href="{GUIDES.BUILD_DATA_SOURCE.href}#managing-access-to-the-data-source"> |
| 278 | + Managing access to the data source |
| 279 | + </A> |
| 280 | + section of the |
| 281 | + <A href={GUIDES.BUILD_DATA_SOURCE.href}>{GUIDES.BUILD_DATA_SOURCE.title}</A> |
| 282 | + guide also explains how to grant access to the data source to other groups and how to remove access. |
| 283 | + Granting them access means they will be able to generate API tokens with their group as the |
| 284 | + <em>sub</em>. |
| 285 | + </p> |
| 286 | +</TableOfContents> |
0 commit comments