Skip to content

Commit ea5d77a

Browse files
Move the "Verifying API tokens" guide to this repo + improvements to "Building a data source" guide. (#33)
1 parent 2562e53 commit ea5d77a

9 files changed

Lines changed: 330 additions & 7 deletions

File tree

src/lib/components/Code.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
let code = ""
1616
1717
$: {
18-
code = codeContainer ? codeContainer.innerHTML.trim() : ""
18+
code = codeContainer ? codeContainer.textContent.trim() : ""
1919
}
2020
</script>
2121

src/routes/guides/building-a-data-source/+page.svelte

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -170,15 +170,27 @@
170170
</p>
171171
<GuideImage img={images.CREATE_NEW_DS} />
172172
<p>Press the <em>Create</em> button to create the data source.</p>
173+
174+
<SectionTitle title="Managing access to the data source" />
173175
<p>
174-
As we selected <em>Dataspace verified API tokens</em>, after group is created you will see the
175-
<em>Allowed groups</em> section:
176+
As we selected <em>Dataspace verified API tokens</em> when creating the data source, you will
177+
see the
178+
<em>Allowed groups</em> section at the bottom of the page once the source has been created:
176179
</p>
177180
<GuideImage img={images.ALLOWED_GROUPS} />
178181
<p>
179-
Each group that wants to access data from this data source, needs to be in this list. You don't
180-
need to add your own group explicitly, but if you want to add other groups for testing purposes,
181-
- click <em>+ Add</em>, type in the name of the group, click <em>+ Add</em> again.
182+
Each group that should be able to access data from this data source, needs to be in this list.
183+
Your own group is added automatically, but if you want to add other groups, click
184+
<em>+ Add</em>, type in the name of the group, click <em>+ Add</em> again.
185+
</p>
186+
<p>
187+
You likely also want to check the API token in your own data source, at least for any real
188+
integrations accessing any data that shouldn't be publicly accessible, by following the
189+
instructions in the
190+
<A href={GUIDES.VERIFYING_API_TOKENS.href}>
191+
{GUIDES.VERIFYING_API_TOKENS.title}
192+
</A> -guide. You can also use the name of the group, which is found in the <em>sub</em> field of
193+
the token, to restrict which group should have access to what data.
182194
</p>
183195
<p>
184196
To test your data source, you can either follow the more in-depth steps in the next

src/routes/guides/images.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,20 @@
6767
"alt": "Swagger UI Responses section showing the curl command and server response."
6868
}
6969
},
70+
"VERIFYING_API_TOKENS": {
71+
"JWT_IO_DECODED_TOKEN": {
72+
"src": "/guides/verifying-api-tokens/jwt_io_decoded_token.png",
73+
"alt": "Interface of JWT.io showing tje decoded and verified token."
74+
},
75+
"AZURE_API_MANAGEMENT_POLICY": {
76+
"src": "/guides/verifying-api-tokens/azure_api_management_policy.png",
77+
"alt": "Azure API Management screen with inbound processing policy for validating JWT."
78+
},
79+
"SWAGGER_UI_RESPONSE_CURL": {
80+
"src": "/guides/verifying-api-tokens/swagger_ui_response_curl.png",
81+
"alt": "Swagger UI Responses section highlighting the curl command."
82+
}
83+
},
7084
"BUILD_APP": {
7185
"API_DOCS": {
7286
"src": "/guides/building-an-application/api_docs.png",

src/routes/guides/urls.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export const GUIDES = {
1717
},
1818
VERIFYING_API_TOKENS: {
1919
title: "Verifying API tokens",
20-
href: "https://ioxio.notion.site/Verifying-API-tokens-1528e3ae865b804c9f2dd354b143c743",
20+
href: "/guides/verifying-api-tokens",
2121
},
2222
BUILD_APP: {
2323
title: "Legacy: Building an application",
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import IMAGES from "../images.json"
2+
import { GUIDES } from "../urls"
3+
4+
export async function load({ url, route }) {
5+
return {
6+
path: url.pathname,
7+
route: route.id,
8+
guide: GUIDES.VERIFYING_API_TOKENS,
9+
images: IMAGES.VERIFYING_API_TOKENS,
10+
}
11+
}
Lines changed: 286 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,286 @@
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://&lt;dataspace-domain&gt;/.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>
67.7 KB
Loading
164 KB
Loading
64.9 KB
Loading

0 commit comments

Comments
 (0)