Skip to content

Commit 31e77bf

Browse files
authored
Add support for self issued jwt tokens (#87)
* FUND-2013 Add support for zgw jwt tokens * FUND-2013 Add support for zgw jwt tokens * FUND-2013 Add documentation
1 parent 105e457 commit 31e77bf

File tree

18 files changed

+209
-136
lines changed

18 files changed

+209
-136
lines changed

AUTHENTICATION.md

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
# Authentication for OneGround ZGW APIs
2+
3+
This guide explains how to authenticate against the ZGW APIs during local development and demos.
4+
5+
## Supported token types
6+
7+
- **OAuth2 access tokens (recommended)**
8+
- Issued via standard flows (e.g. client credentials as in this guide).
9+
- Tokens are verified by checking the signature with the Identity Provider's public keys (JWKS), enabling offline validation and reducing network calls.
10+
- Tokens are issued in one place (the Identity Provider), so you can centrally enforce short expiration times, rotate keys, and revoke compromised clients.
11+
- Any standards‑compliant OAuth2 Identity Provider can be used to issue access tokens for these APIs. Keycloak is used in this repository only as an example.
12+
- Each access token must include an `rsin` claim that contains the organization's RSIN. APIs use this claim for tenant/organization context. In this local Keycloak setup, `rsin` claim is added via hardcoded claim mapper on the api client. When using a different Identity Provider, configure an equivalent claim/attribute mapping so that issued access tokens contain the `rsin` claim.
13+
14+
- **ZGW standard tokens (legacy/backwards compatibility)**
15+
- Self‑issued JWTs signed with HS256 (HMAC‑SHA256) using the Keycloak client's secret.
16+
- Must include a `client_id` claim that matches a client in the Keycloak realm.
17+
- Supported only with Keycloak together with the custom token introspection plugin. See the project for details: [Keycloak-ZGW-Token-Introspection](https://github.com/OneGround/Keycloak-ZGW-Token-Introspection).
18+
- Use short token lifetimes. Tokens without an `exp` claim are treated as active, but it's not recommended to use non expiring access tokens.
19+
20+
## API Authentication using OAuth2 access tokens
21+
22+
#### Get the Client Secret from Keycloak
23+
24+
1. Navigate to the Keycloak admin console: [http://localhost:8080/admin/master/console/#/OneGround/](http://localhost:8080/admin/master/console/#/OneGround/)
25+
2. Log in using the credentials:
26+
- **Username**: `admin`
27+
- **Password**: `admin`
28+
3. From the navigation on the left, select **Clients**.
29+
4. Select the `oneground-000000000` client from the list.
30+
> **Note on the Default Client:** This local setup is configured with a single default client, `oneground-000000000`, which has full administrative access to all APIs. If you wish to add more clients with specific permissions, you must first create them in Keycloak by following the [Keycloak Setup Guide](./localdev/keycloak/KeycloakSetup/README.md). After creating a new client, you must also configure its permissions using the Autorisaties API or by updating the [autorisaties service's seed data](./localdev/oneground-services-data/ac-data/applicaties.json).
31+
5. Go to the **Credentials** tab.
32+
6. Copy the value from the **Client Secret** field. This is your `<oneground-client-secret>`.
33+
34+
#### Request an Access Token
35+
36+
Now you can exchange the client credentials for a temporary access token. Use the command for your operating system, replacing `<oneground-client-secret>` with your actual secret. The default client ID is `oneground-000000000`.
37+
38+
##### For Windows (PowerShell)
39+
40+
- Open Windows PowerShell and execute this command:
41+
42+
```powershell
43+
$response = Invoke-WebRequest `
44+
-Uri "http://localhost:8080/realms/OneGround/protocol/openid-connect/token" `
45+
-Method POST `
46+
-Headers @{"Content-Type" = "application/x-www-form-urlencoded"} `
47+
-Body "grant_type=client_credentials&client_id=oneground-000000000&client_secret=<oneground-client-secret>"
48+
```
49+
50+
- Then take an access token from `$response`:
51+
52+
```powershell
53+
$response.Content
54+
```
55+
56+
##### For Linux, macOS, or WSL (cURL)
57+
58+
- Open terminal and execute this command:
59+
60+
```bash
61+
curl --location --request POST 'http://localhost:8080/realms/OneGround/protocol/openid-connect/token' \
62+
--header 'Content-Type: application/x-www-form-urlencoded' \
63+
--data-urlencode 'grant_type=client_credentials' \
64+
--data-urlencode 'client_id=oneground-000000000' \
65+
--data-urlencode 'client_secret=<oneground-client-secret>'
66+
```
67+
68+
You will receive a JSON response containing the `access_token`. You can now use this token as a `Bearer` token to authorize your API requests.
69+
70+
```json
71+
{
72+
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAi......",
73+
"expires_in": 300,
74+
"refresh_expires_in": 0,
75+
"token_type": "Bearer"
76+
}
77+
```
78+
79+
> **Tip: How to Increase Token Expiration Time (For Testing Only)**
80+
>
81+
> **Warning:** Extending access token lifespans reduces security. Long-lived tokens are easier to steal and misuse and increase the impact of any leak because they remain valid for longer. Only increase token lifespans for local testing in non-production environments.
82+
> By default, the access token expires in 5 minutes (300 seconds). To increase this time:
83+
>
84+
> 1. Navigate directly to the **Tokens** settings page in Keycloak: [http://localhost:8080/admin/master/console/#/OneGround/realm-settings/tokens](http://localhost:8080/admin/master/console/#/OneGround/realm-settings/tokens).
85+
> 2. In the `Access Token Lifespan` field, set a longer duration (e.g., `30 minutes` or `1 hour`).
86+
> 3. Click **Save**.
87+
>
88+
> You will need to request a new token for this change to take effect.

getting-started/docker-compose/README.md

Lines changed: 3 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,10 @@
1414
- [Step 5.1: Get the Client Secret from Keycloak](#step-51-get-the-client-secret-from-keycloak)
1515
- [Step 5.2: Update Environment File and Restart Services](#step-52-update-environment-file-and-restart-services)
1616
- [Step 5.3: Request an Access Token](#step-53-request-an-access-token)
17-
- [For Windows (PowerShell)](#for-windows-powershell)
18-
- [For Linux, macOS, or WSL (cURL)](#for-linux-macos-or-wsl-curl)
1917
- [Step 5.4: Creating a Sample Case Using Postman](#step-54-creating-a-sample-case-using-postman)
2018
- [6. Stopping the Services](#6-stopping-the-services)
2119
- [Service Endpoints and Tools](#service-endpoints-and-tools)
22-
- [ZGW API Services](#zgw-api-services)
20+
- [ZGW API Services/Listeners](#zgw-api-serviceslisteners)
2321
- [Hosted Tools](#hosted-tools)
2422
- [License](#license)
2523

@@ -160,15 +158,7 @@ To make authorized requests to the APIs, you first need to get a client secret f
160158

161159
#### Step 5.1: Get the Client Secret from Keycloak
162160

163-
1. Navigate to the Keycloak admin console: [http://localhost:8080/admin/master/console/#/OneGround/](http://localhost:8080/admin/master/console/#/OneGround/)
164-
2. Log in using the credentials:
165-
- **Username**: `admin`
166-
- **Password**: `admin`
167-
3. From the navigation on the left, select **Clients**.
168-
4. Select the `oneground-000000000` client from the list.
169-
> **Note on the Default Client:** This local setup is configured with a single default client, `oneground-000000000`, which has full administrative access to all APIs. If you wish to add more clients with specific permissions, you must first create them in Keycloak by following the [Keycloak Setup Guide](../../localdev/keycloak/KeycloakSetup/README.md). After creating a new client, you must also configure its permissions using the Autorisaties API or by updating the [autorisaties service's seed data](../oneground-services-data/ac-data/applicaties.json).
170-
5. Go to the **Credentials** tab.
171-
6. Copy the value from the **Client Secret** field. This is your `<oneground-client-secret>`.
161+
See [AUTHENTICATION.md](../../AUTHENTICATION.md).
172162

173163
#### Step 5.2: Update Environment File and Restart Services
174164

@@ -194,57 +184,7 @@ To make authorized requests to the APIs, you first need to get a client secret f
194184

195185
#### Step 5.3: Request an Access Token
196186

197-
This process uses the standard OAuth 2.0 Client Credentials grant type to obtain an access token. Now you can exchange the client credentials for a temporary access token. Use the command for your operating system, replacing `<oneground-client-secret>` with your actual secret. The default client ID is `oneground-000000000`.
198-
199-
##### For Windows (PowerShell)
200-
201-
- Open Windows PowerShell and execute this command:
202-
203-
```powershell
204-
$response = Invoke-WebRequest `
205-
-Uri "http://localhost:8080/realms/OneGround/protocol/openid-connect/token" `
206-
-Method POST `
207-
-Headers @{"Content-Type" = "application/x-www-form-urlencoded"} `
208-
-Body "grant_type=client_credentials&client_id=oneground-000000000&client_secret=<oneground-client-secret>"
209-
```
210-
211-
- Then take an access token from `$response`:
212-
213-
```powershell
214-
$response.Content
215-
```
216-
217-
##### For Linux, macOS, or WSL (cURL)
218-
219-
- Open terminal and execute this command:
220-
221-
```bash
222-
curl --location --request POST 'http://localhost:8080/realms/OneGround/protocol/openid-connect/token' \
223-
--header 'Content-Type: application/x-www-form-urlencoded' \
224-
--data-urlencode 'grant_type=client_credentials' \
225-
--data-urlencode 'client_id=oneground-000000000' \
226-
--data-urlencode 'client_secret=<oneground-client-secret>'
227-
```
228-
229-
You will receive a JSON response containing the `access_token`. You can now use this token as a `Bearer` token to authorize your API requests.
230-
231-
```json
232-
{
233-
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAi......",
234-
"expires_in": 300,
235-
"refresh_expires_in": 0,
236-
"token_type": "Bearer"
237-
}
238-
```
239-
240-
> **Tip: How to Increase Token Expiration Time**
241-
> By default, the access token expires in 5 minutes (300 seconds). To increase this time:
242-
>
243-
> 1. Navigate directly to the **Tokens** settings page in Keycloak: [http://localhost:8080/admin/master/console/#/OneGround/realm-settings/tokens](http://localhost:8080/admin/master/console/#/OneGround/realm-settings/tokens).
244-
> 2. In the `Access Token Lifespan` field, set a longer duration (e.g., `30 minutes` or `1 hour`).
245-
> 3. Click **Save**.
246-
>
247-
> You will need to request a new token for this change to take effect.
187+
See [AUTHENTICATION.md](../../AUTHENTICATION.md).
248188

249189
#### Step 5.4: Creating a Sample Case Using Postman
250190

getting-started/docker-compose/docker-compose.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,13 @@ services:
8383
networks:
8484
- oneground
8585

86+
keycloak-provider-downloader:
87+
image: alpine:3.22
88+
command: sh -c "if [ -f /providers/zgw-token-introspection.jar ]; then echo 'JAR present'; else wget -q -O /providers/zgw-token-introspection.jar https://github.com/OneGround/Keycloak-ZGW-Token-Introspection/releases/download/v1/zgw-token-introspection.jar && chmod 644 /providers/zgw-token-introspection.jar; fi"
89+
volumes:
90+
- kc-providers:/providers
91+
restart: "no"
92+
8693
keycloak:
8794
image: quay.io/keycloak/keycloak:26.3.1
8895
restart: unless-stopped
@@ -107,9 +114,13 @@ services:
107114
retries: 8
108115
start_period: 5s
109116
command: ["start-dev"]
117+
volumes:
118+
- kc-providers:/opt/keycloak/providers:ro
110119
depends_on:
111120
postgres_docker_db:
112121
condition: service_healthy
122+
keycloak-provider-downloader:
123+
condition: service_completed_successfully
113124
networks:
114125
- oneground
115126

@@ -363,6 +374,7 @@ services:
363374

364375
volumes:
365376
postgres:
377+
kc-providers:
366378

367379
networks:
368380
oneground:

localdev/README.md

Lines changed: 3 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,9 @@
1414
- [Step 5.1: Get the Client Secret from Keycloak](#step-51-get-the-client-secret-from-keycloak)
1515
- [Step 5.2: Update Environment File and Restart Services](#step-52-update-environment-file-and-restart-services)
1616
- [Step 5.3: Request an Access Token](#step-53-request-an-access-token)
17-
- [For Windows (PowerShell)](#for-windows-powershell)
18-
- [For Linux, macOS, or WSL (cURL)](#for-linux-macos-or-wsl-curl)
1917
- [6. Stopping the Services](#6-stopping-the-services)
2018
- [Service Endpoints and Tools](#service-endpoints-and-tools)
21-
- [ZGW API Services](#zgw-api-services)
19+
- [ZGW API Services/listeners](#zgw-api-serviceslisteners)
2220
- [Hosted Tools](#hosted-tools)
2321

2422
## About This Guide
@@ -163,15 +161,7 @@ To make authorized requests to the APIs, you first need to get a client secret f
163161
164162
#### Step 5.1: Get the Client Secret from Keycloak
165163
166-
1. Navigate to the Keycloak admin console: [http://localhost:8080/admin/master/console/#/OneGround/](http://localhost:8080/admin/master/console/#/OneGround/)
167-
2. Log in using the credentials:
168-
- **Username**: `admin`
169-
- **Password**: `admin`
170-
3. From the navigation on the left, select **Clients**.
171-
4. Select the `oneground-000000000` client from the list.
172-
> **Note on the Default Client:** This local setup is configured with a single default client, `oneground-000000000`, which has full administrative access to all APIs. If you wish to add more clients with specific permissions, you must first create them in Keycloak by following the [Keycloak Setup Guide](./keycloak/KeycloakSetup/README.md). After creating a new client, you must also configure its permissions using the Autorisaties API or by updating the [autorisaties service's seed data](./oneground-services-data/ac-data/applicaties.json).
173-
5. Go to the **Credentials** tab.
174-
6. Copy the value from the **Client Secret** field. This is your `<oneground-client-secret>`.
164+
See [AUTHENTICATION.md](../AUTHENTICATION.md).
175165
176166
#### Step 5.2: Update Environment File and Restart Services
177167
@@ -197,57 +187,7 @@ To make authorized requests to the APIs, you first need to get a client secret f
197187
198188
#### Step 5.3: Request an Access Token
199189
200-
Now you can exchange the client credentials for a temporary access token. Use the command for your operating system, replacing `<oneground-client-secret>` with your actual secret. The default client ID is `oneground-000000000`.
201-
202-
##### For Windows (PowerShell)
203-
204-
- Open Windows PowerShell and execute this command:
205-
206-
```powershell
207-
$response = Invoke-WebRequest `
208-
-Uri "http://localhost:8080/realms/OneGround/protocol/openid-connect/token" `
209-
-Method POST `
210-
-Headers @{"Content-Type" = "application/x-www-form-urlencoded"} `
211-
-Body "grant_type=client_credentials&client_id=oneground-000000000&client_secret=<oneground-client-secret>"
212-
```
213-
214-
- Then take an access token from `$response`:
215-
216-
```powershell
217-
$response.Content
218-
```
219-
220-
##### For Linux, macOS, or WSL (cURL)
221-
222-
- Open terminal and execute this command:
223-
224-
```bash
225-
curl --location --request POST 'http://localhost:8080/realms/OneGround/protocol/openid-connect/token' \
226-
--header 'Content-Type: application/x-www-form-urlencoded' \
227-
--data-urlencode 'grant_type=client_credentials' \
228-
--data-urlencode 'client_id=oneground-000000000' \
229-
--data-urlencode 'client_secret=<oneground-client-secret>'
230-
```
231-
232-
You will receive a JSON response containing the `access_token`. You can now use this token as a `Bearer` token to authorize your API requests.
233-
234-
```json
235-
{
236-
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAi......",
237-
"expires_in": 300,
238-
"refresh_expires_in": 0,
239-
"token_type": "Bearer"
240-
}
241-
```
242-
243-
> **Tip: How to Increase Token Expiration Time**
244-
> By default, the access token expires in 5 minutes (300 seconds). To increase this time:
245-
>
246-
> 1. Navigate directly to the **Tokens** settings page in Keycloak: [http://localhost:8080/admin/master/console/#/OneGround/realm-settings/tokens](http://localhost:8080/admin/master/console/#/OneGround/realm-settings/tokens).
247-
> 2. In the `Access Token Lifespan` field, set a longer duration (e.g., `30 minutes` or `1 hour`).
248-
> 3. Click **Save**.
249-
>
250-
> You will need to request a new token for this change to take effect.
190+
See [AUTHENTICATION.md](../AUTHENTICATION.md).
251191
252192
### 6. Stopping the Services
253193

localdev/docker-compose.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,13 @@ services:
106106
networks:
107107
- oneground
108108

109+
keycloak-provider-downloader:
110+
image: alpine:3.22
111+
command: sh -c "if [ -f /providers/zgw-token-introspection.jar ]; then echo 'JAR present'; else wget -q -O /providers/zgw-token-introspection.jar https://github.com/OneGround/Keycloak-ZGW-Token-Introspection/releases/download/v1/zgw-token-introspection.jar && chmod 644 /providers/zgw-token-introspection.jar; fi"
112+
volumes:
113+
- kc-providers:/providers
114+
restart: "no"
115+
109116
keycloak:
110117
image: quay.io/keycloak/keycloak:26.3.1
111118
restart: unless-stopped
@@ -130,9 +137,13 @@ services:
130137
retries: 8
131138
start_period: 5s
132139
command: ["start-dev"]
140+
volumes:
141+
- kc-providers:/opt/keycloak/providers:ro
133142
depends_on:
134143
postgres_docker_db:
135144
condition: service_healthy
145+
keycloak-provider-downloader:
146+
condition: service_completed_successfully
136147
networks:
137148
- oneground
138149

@@ -387,6 +398,7 @@ volumes:
387398
postgres:
388399
ceph-etc:
389400
ceph-lib:
401+
kc-providers:
390402

391403
networks:
392404
oneground:

src/Directory.Packages.props

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
<PackageVersion Include="CSharpier.MsBuild" Version="1.0.2" />
1010
<PackageVersion Include="CsvHelper" Version="33.0.1" />
1111
<PackageVersion Include="Dapper" Version="2.0.123" />
12+
<PackageVersion Include="Duende.AspNetCore.Authentication.OAuth2Introspection" Version="6.3.0" />
1213
<PackageVersion Include="Duende.IdentityModel" Version="7.0.0" />
1314
<PackageVersion Include="FluentValidation" Version="12.0.0" />
1415
<PackageVersion Include="FluentValidation.DependencyInjectionExtensions" Version="12.0.0" />
@@ -62,4 +63,4 @@
6263
<PackageVersion Include="Swashbuckle.AspNetCore.Filters" Version="8.0.3" />
6364
<PackageVersion Include="Swashbuckle.AspNetCore.Newtonsoft" Version="8.1.1" />
6465
</ItemGroup>
65-
</Project>
66+
</Project>

src/OneGround.ZGW.Autorisaties.WebApi/Program.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using OneGround.ZGW.Autorisaties.Web;
33
using OneGround.ZGW.Autorisaties.Web.Services;
44
using OneGround.ZGW.Common.Constants;
5+
using OneGround.ZGW.Common.Web.Authentication;
56
using OneGround.ZGW.Common.Web.Configuration;
67
using OneGround.ZGW.Common.Web.Extensions.ServiceCollection;
78

src/OneGround.ZGW.Besluiten.WebApi/Program.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using OneGround.ZGW.Autorisaties.ServiceAgent;
33
using OneGround.ZGW.Besluiten.Web;
44
using OneGround.ZGW.Common.Constants;
5+
using OneGround.ZGW.Common.Web.Authentication;
56
using OneGround.ZGW.Common.Web.Configuration;
67
using OneGround.ZGW.Common.Web.Extensions.ServiceCollection;
78

src/OneGround.ZGW.Catalogi.WebApi/Program.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using OneGround.ZGW.Autorisaties.ServiceAgent;
33
using OneGround.ZGW.Catalogi.Web;
44
using OneGround.ZGW.Common.Constants;
5+
using OneGround.ZGW.Common.Web.Authentication;
56
using OneGround.ZGW.Common.Web.Configuration;
67
using OneGround.ZGW.Common.Web.Extensions.ServiceCollection;
78

0 commit comments

Comments
 (0)