You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: gds-draft/authorization.md
+8-6Lines changed: 8 additions & 6 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -29,7 +29,7 @@ views {
29
29
variant sequence
30
30
31
31
david -> charlie 'GET /buildings/{vboId} + Bearer token'
32
-
charlie -> charlie 'Validate token & extract organization'
32
+
charlie -> charlie 'Validate token & derive organization EUID'
33
33
charlie -> ar 'GET /api/authorization/explained-enforce'
34
34
ar -> charlie 'HTTP 200: {allowed: true/false, policies}'
35
35
charlie -> david 'If allowed: 200 + data / If denied: 403'
@@ -41,6 +41,8 @@ views {
41
41
42
42
When a building owner (Bob) approves an access request through Keyper, policies are registered in the GDS Authorization Registry. Each policy specifies:
43
43
44
+
For GDS, EUID is the chosen identifier format for policy identities. This means `issuerId`, `subjectId`, and `serviceProvider` are EUID values.
45
+
44
46
| Field | Description | Example |
45
47
|-------|-------------|---------|
46
48
|`issuerId`| Building owner who granted access |`NLNHR.87654321`|
@@ -76,9 +78,9 @@ GET https://gds-preview.poort8.nl/api/authorization/explained-enforce?issuer={BU
76
78
77
79
| Parameter | Description | Example |
78
80
|-----------|-------------|---------|
79
-
|`issuer`| Building owner who granted access (Bob) |`NLNHR.87654321`|
80
-
|`subject`| Organization requesting data (David) |`NLNHR.12345678`|
81
-
|`serviceProvider`| Your organization (Charlie) |`NLNHR.23456789`|
81
+
|`issuer`| Building owner who granted access (Bob), as EUID|`NLNHR.87654321`|
82
+
|`subject`| Organization requesting data (David), as EUID|`NLNHR.12345678`|
83
+
|`serviceProvider`| Your organization (Charlie), as EUID|`NLNHR.23456789`|
82
84
|`action`| Requested action |`GET`|
83
85
|`resource`| Resource identifier (VBO ID or asset ID) |`0363010000659001`|
84
86
|`type`| Resource type |`building`|
@@ -134,7 +136,7 @@ The endpoint always returns HTTP 200 — even when authorization is denied. When
134
136
| Check | Requirement |
135
137
|-------|-------------|
136
138
|**Allowed**|`allowed` must be `true`|
137
-
|**Subject match**|`explainPolicies[].subjectId` must match the organization extracted from the incoming request's bearer token|
139
+
|**Subject match**|`explainPolicies[].subjectId` must match the EUID derived from the incoming request's `organization` claim|
138
140
139
141
### Recommended responses to your consumers
140
142
@@ -155,7 +157,7 @@ Putting it all together — a simplified enforcement flow:
155
157
```
156
158
1. Receive request with Bearer token and resource identifier
| 4 |**Audience**|`aud` contains your API's client ID |`403 Forbidden`|
60
-
| 5 |**Organization**|`organization` claim is present | Use for business logic |
60
+
| 5 |**Organization**|`organization` claim is present and contains an `EUID` value | Use for business logic |
61
61
62
62
> **Step 4 is critical.** Without audience validation, a token intended for a different API could be used to access yours. Always verify that your API's client ID appears in the `aud` claim.
63
63
@@ -87,6 +87,15 @@ A decoded access token from a GDS consumer:
87
87
"client_id": "CONSUMER_APP_CLIENT_ID",
88
88
"organization": {
89
89
"NLNHR.12345678": {
90
+
"KVK": [
91
+
"12345678"
92
+
],
93
+
"EORI": [
94
+
"NL123456789"
95
+
],
96
+
"EUID": [
97
+
"NLNHR.12345678"
98
+
],
90
99
"id": "550e8400-e29b-41d4-a716-446655440000"
91
100
}
92
101
}
@@ -102,23 +111,52 @@ A decoded access token from a GDS consumer:
102
111
|`iat`| number | Issued-at time (Unix timestamp) |
|`organization`| object | Consumer's verified organization identity with one or more identifier types |
106
115
107
-
## Organization claim
116
+
## Deriving the organization identifier
108
117
109
-
The `organization` claim identifies the consumer's organization:
118
+
After successful token validation, derive the caller organization identifier from the `organization` claim.
119
+
120
+
The `organization` claim is a Keycloak-specific JSON structure. For each organization it can contain multiple identifier types represented as arrays. The chosen identifier in GDS is EUID.
121
+
122
+
In GDS, EUID is the canonical identifier for authorization. Use the derived EUID consistently as:
123
+
124
+
-`subject` in explained-enforce requests
125
+
-`organizationId` in approval request payloads
126
+
-`subjectId`, `issuerId`, and `serviceProvider` values in policy payloads
127
+
128
+
Use this algorithm:
129
+
130
+
1. Iterate through organizations in the `organization` object.
131
+
2. For each organization, check whether the EUID attribute exists and has a non-empty array.
132
+
3. Take the first array item as the caller organization identifier.
133
+
134
+
Example structure:
110
135
111
136
```json
112
137
{
113
138
"organization": {
114
139
"NLNHR.12345678": {
140
+
"KVK": [
141
+
"12345678"
142
+
],
143
+
"EORI": [
144
+
"NL123456789"
145
+
],
146
+
"EUID": [
147
+
"NLNHR.12345678"
148
+
],
115
149
"id": "550e8400-e29b-41d4-a716-446655440000"
116
150
}
117
151
}
118
152
}
119
153
```
120
154
121
-
The key (e.g., `NLNHR.12345678`) is the organization's **EUID** — derived from the official KvK registration number and verified during onboarding. This is not a self-declared value.
155
+
Reject the request with `403 Forbidden` when:
156
+
157
+
- The `organization` claim is missing
158
+
- The `organization` claim is not a valid JSON object
159
+
- The `EUID` attribute is missing or empty for all organizations
0 commit comments