From standaarden
Guides implementation of Federated Service Connectivity (FSC, formerly NLX) for Dutch government APIs, covering inways, outways, service directories, peers, external contracts, and Manager API.
npx claudepluginhub developer-overheid-nl/skills-marketplace --plugin standaardenThis skill is limited to using the following tools:
> **CONCEPT — Let op:** Deze skill is geen officieel product van Logius. De beschrijvingen zijn informatieve samenvattingen — niet de officiële standaarden zelf. De definities op [forumstandaardisatie.nl](https://www.forumstandaardisatie.nl/open-standaarden) en [Logius](https://www.logius.nl) zijn altijd leidend. Overheidsorganisaties die generatieve AI inzetten dienen te voldoen aan het [Overh...
Monitors deployed URLs for regressions after deploys, merges, or upgrades by checking HTTP status, console errors, network failures, performance (LCP/CLS/INP), content, and API health.
Share bugs, ideas, or general feedback.
CONCEPT — Let op: Deze skill is geen officieel product van Logius. De beschrijvingen zijn informatieve samenvattingen — niet de officiële standaarden zelf. De definities op forumstandaardisatie.nl en Logius zijn altijd leidend. Overheidsorganisaties die generatieve AI inzetten dienen te voldoen aan het Overheidsbreed standpunt voor de inzet van generatieve AI. Zie DISCLAIMER.md en onze verantwoording.
Agent-instructie: Deze skill helpt bij het implementeren van FSC (Federated Service Connectivity) voor federatieve dienstverlening. FSC gebruikt mTLS, OAuth 2.0 client_credentials en cryptografisch ondertekende contracten. Vereist bij Digikoppeling REST-API (v3.0.1+; huidige publicatie v4.0.0); geen afzonderlijke vermelding op het Forum Standaardisatie.
Net als andere Logius-standaarden kent FSC twee publicatiekanalen (vergelijkbaar met W3C):
gitdocumentatie.logius.nllogius-standaarden.github.ioFSC heeft vastgestelde versies voor fsc-core en fsc-logging. De overige modules hebben alleen werkversies; fsc-external-contract heeft een consultatieversie (CV). Modulestatus hangt samen met de core-specificatie maar wordt apart vastgesteld.
| Repository | Beschrijving | Licentie | Vastgesteld | Draft |
|---|---|---|---|---|
| fsc-core | Core specificatie: architectuur, protocol, componenten | CC-BY-4.0 | v2.0.0 | Draft |
| fsc-logging | Module: logging specificatie voor FSC transacties | CC-BY-4.0 | v1.1.0 | Draft |
| fsc-properties | Module: metadata-properties voor FSC services | CC-BY-4.0 | - | Draft |
| fsc-regulated-area | Module: Regulated Area specificatie (governance zones) | CC-BY-4.0 | - | Draft |
| fsc-external-contract | Module: External Contract specificatie (afspraken tussen partijen) | CC-BY-4.0 | CV v1.0.0¹ | Draft |
| fsc-extensie-template | Richtlijn voor nieuwe FSC extensies | CC-BY-4.0 | - | Draft |
| fsc-test-suite | Integratietests en componenttests | Niet gespecificeerd | - | - |
¹ CV = consultatieversie, nog niet definitief vastgesteld.
Het proces waarmee een consumer-organisatie een service van een provider-organisatie afneemt verloopt in de volgende stappen:
De consumer maakt een Contract aan met daarin een ServiceConnectionGrant. Dit grant specificeert welke service de consumer wil afnemen en bij welke provider.
De consumer ondertekent het contract cryptografisch met zijn eigen certificaat. De handtekening bevat de certificate thumbprint (SHA-256).
Het ondertekende contract wordt via de Manager API (POST /contracts) naar de Manager van de provider gestuurd.
De provider beoordeelt het contract en ondertekent het bij akkoord (PUT /contracts/{hash}/accept). Het contract is nu geldig en beide partijen hebben een ondertekend exemplaar.
De consumer (via de Outway) vraagt een access token aan bij de Manager van de provider via het OAuth 2.0 Client Credentials grant:
POST /token HTTP/1.1
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials
&scope=<GrantHash>
&client_id=<PeerID>
grant_type: altijd client_credentialsscope: de hash van het specifieke Grant dat de consumer wil gebruikenclient_id: het PeerID van de consumer (afgeleid uit het certificaat)De Manager valideert de aanvraag, controleert of er een geldig contract bestaat met het opgegeven grant, en geeft een JWT access token terug.
De Outway van de consumer stuurt het HTTP-verzoek naar de Inway van de provider. Het access token wordt meegegeven in de Fsc-Authorization header:
GET /service/endpoint HTTP/1.1
Host: inway.provider.example.nl
Fsc-Authorization: Bearer <JWT-token>
De Inway van de provider:
Fsc-Authorization headeraud, svc, gth, gid, cnf)cnf thumbprintEen contract heeft de volgende structuur (conform fsc-core specificatie):
{
"content": {
"fsc_version": "1.0.0",
"iv": "06338364-8305-7b74-8000-de4963503139",
"group_id": "nl-overheid-productie",
"validity": {
"not_before": 1672527600,
"not_after": 1704063600
},
"grants": [
{
"data": {
"type": "GRANT_TYPE_SERVICE_CONNECTION",
"service": {
"type": "SERVICE_TYPE_SERVICE",
"peer_id": "00000000000000000001",
"name": "mijn-service"
},
"outway": {
"peer_id": "00000000000000000002",
"identification": {
"type": "OUTWAY_IDENTIFICATION_TYPE_PUBLIC_KEY_THUMBPRINT",
"public_key_thumbprint": "3a56f2e9269ac63f0d4394c46b96539da1625b6a985d38029ff89f34e490960c"
}
}
}
}
],
"hash_algorithm": "HASH_ALGORITHM_SHA3_512",
"created_at": 1672527600
},
"signatures": {
"accept": {
"00000000000000000001": "eyJhbGciOiJSUzUxMiIsIng1dCNTMjU2Ijo... <JWS compact serialization>",
"00000000000000000002": "eyJhbGciOiJFUzI1NiIsIng1dCNTMjU2Ijo... <JWS compact serialization>"
},
"reject": {},
"revoke": {}
}
}
Polymorfe velden:
servicekan ookSERVICE_TYPE_DELEGATED_SERVICEzijn (extra velddelegator).outway.identificationkan ookOUTWAY_IDENTIFICATION_TYPE_DOMAIN_NAMEzijn (velddomain_name). Grants kunnen optioneel eenproperties-object bevatten. Zie de fsc-core OpenAPI spec voor het volledige schema.
| Grant Type | Gebruik | Partijen |
|---|---|---|
| ServicePublicationGrant | Service publiceren in Directory | Provider + Directory |
| DelegatedServicePublicationGrant | Service publiceren namens andere Peer | Gemachtigde + Eigenaar + Directory |
| ServiceConnectionGrant | Toegang tot service (meest gebruikt) | Consumer + Provider |
| DelegatedServiceConnectionGrant | Toegang namens andere consumer | Gemachtigde + Consumer + Provider |
Het access token is een JSON Web Token (JWT) dat wordt uitgegeven door de Manager van de provider. Het token bevat de volgende claims:
| Claim | Beschrijving |
|---|---|
sub | Subject: het PeerID van de consumer die de service afneemt |
iss | Issuer: het PeerID van de provider die het token uitgeeft |
svc | Service: de naam van de service waarvoor het token geldig is |
aud | Audience: de URL van de Inway van de provider |
gth | Grant Hash: de hash van het specifieke grant in het contract |
gid | Group ID: identifier van de Group waarbinnen het token geldig is |
cnf | Confirmation: object met x5t#S256 (SHA-256 thumbprint van het client-certificaat) |
exp | Expiration: verloopdatum van het token |
nbf | Not Before: tijdstip vanaf wanneer het token geldig is |
De cnf claim bindt het token aan het specifieke certificaat van de consumer, waardoor token-diefstal wordt voorkomen (certificate-bound tokens).
{
"sub": "PeerID-consumer",
"iss": "PeerID-provider",
"svc": "mijn-service",
"aud": "https://inway.provider.example.nl",
"gth": "sha256-hash-van-grant",
"gid": "nl-overheid-productie",
"cnf": {
"x5t#S256": "sha256-thumbprint-van-client-certificaat"
},
"exp": 1735689600,
"nbf": 1735686000
}
De Manager biedt de volgende REST API endpoints:
| Methode | Endpoint | Beschrijving |
|---|---|---|
PUT | /announce | Registreer of update een Peer in het netwerk. Bevat informatie over de Peer zoals naam, Inway-adres en beschikbare services. |
GET | /peer | Haal informatie op over de eigen Peer (self-discovery). |
GET | /peers | Lijst van alle bekende Peers in het netwerk. |
| Methode | Endpoint | Beschrijving |
|---|---|---|
POST | /contracts | Dien een nieuw (ondertekend) contract in bij de tegenpartij. |
GET | /contracts | Haal alle contracten op (met optionele filters). |
PUT | /contracts/{hash}/accept | Accepteer een contract door het te ondertekenen. |
PUT | /contracts/{hash}/reject | Wijs een contract af. |
PUT | /contracts/{hash}/revoke | Trek een eerder geaccepteerd contract in. |
| Methode | Endpoint | Beschrijving |
|---|---|---|
POST | /token | Vraag een access token aan via OAuth 2.0 Client Credentials. Vereist grant_type, scope (GrantHash) en client_id (PeerID). |
| Methode | Endpoint | Beschrijving |
|---|---|---|
GET | /services | Lijst van alle gepubliceerde services (beschikbaar op de Directory). |
FSC definieert twee poorten voor verschillende typen verkeer:
| Poort | Gebruik | Beschrijving |
|---|---|---|
| 443 | Dataverkeer | Standaardpoort voor service-aanroepen tussen Outway en Inway. Draagt de eigenlijke API-verzoeken en responses. |
| 8443 | Managementverkeer | Poort voor communicatie tussen Managers onderling en tussen Outways en Managers. Gebruikt voor contractbeheer, tokenuitgifte en peer-discovery. |
De FSC Logging extensie (fsc-logging) beschrijft hoe transactielogs worden vastgelegd voor audit- en verantwoordingsdoeleinden.
Elk verzoek krijgt een uniek transaction_id mee via de Fsc-Transaction-Id HTTP header. Dit ID wordt door alle componenten in de keten doorgegeven, zodat een volledig pad van consumer naar provider en terug traceerbaar is.
GET /service/endpoint HTTP/1.1
Fsc-Authorization: Bearer <JWT-token>
Fsc-Transaction-Id: 550e8400-e29b-41d4-a716-446655440000
Elk transactielog bevat minimaal de volgende velden:
| Veld | Beschrijving |
|---|---|
transaction_id | Uniek ID van de transactie (UUID), corresponderend met de Fsc-Transaction-Id header |
direction | Richting van het verzoek: in (ontvangen) of out (verzonden) |
grant_hash | Hash van het grant dat is gebruikt voor autorisatie |
source | PeerID van de verzendende partij |
destination | PeerID van de ontvangende partij |
timestamp | Tijdstip van het logmoment |
service | Naam van de aangeroepen service |
direction: out, het gebruikte grant en de bestemmingdirection: in, het gevalideerde grant en de brontransaction_id, waardoor correlatie mogelijk isfrom fastapi import FastAPI, Request, Response
import requests, jwt, time, uuid
app = FastAPI(title="FSC Outway Proxy")
MANAGER_URL = "https://manager.example.com:8443"
MTLS_CERT = ("pkio_cert.pem", "pkio_key.pem")
CA_BUNDLE = "pkio_ca_chain.pem"
# Token cache per grant_hash
token_cache: dict[str, dict] = {}
def get_access_token(grant_hash: str, peer_id: str) -> str:
"""Haal access token op van de Manager (OAuth 2.0 Client Credentials)."""
cached = token_cache.get(grant_hash)
if cached and cached["exp"] > time.time():
return cached["token"]
response = requests.post(
f"{MANAGER_URL}/token",
data={
"grant_type": "client_credentials",
"scope": grant_hash,
"client_id": peer_id,
},
cert=MTLS_CERT,
verify=CA_BUNDLE,
)
response.raise_for_status()
token_data = response.json()
token_cache[grant_hash] = {
"token": token_data["access_token"],
"exp": jwt.decode(token_data["access_token"], options={"verify_signature": False})["exp"]
}
return token_data["access_token"]
@app.api_route("/{service_name}/{path:path}", methods=["GET", "POST", "PUT", "PATCH", "DELETE"])
async def proxy_request(service_name: str, path: str, request: Request):
"""Proxy verzoeken naar een service via FSC Inway."""
grant_hash = resolve_grant_hash(service_name) # uit lokale contracten
inway_url = resolve_inway_url(service_name) # uit directory
access_token = get_access_token(grant_hash, "mijn-oin")
# Forward request naar Inway met Fsc-Authorization header
response = requests.request(
method=request.method,
url=f"{inway_url}/{service_name}/{path}",
headers={
"Fsc-Authorization": f"Bearer {access_token}",
"Fsc-Transaction-Id": str(uuid.uuid4()), # Uniek per request
**{k: v for k, v in request.headers.items()
if k.lower() not in ("host", "fsc-authorization")},
},
data=await request.body(),
cert=MTLS_CERT,
verify=CA_BUNDLE,
)
return Response(
content=response.content,
status_code=response.status_code,
headers=dict(response.headers),
)
import jwt
from cryptography.x509 import load_pem_x509_certificate
from cryptography.hazmat.primitives.hashes import SHA256
import base64
def base64url_encode(data: bytes) -> str:
"""Encode bytes als base64url (zonder padding)."""
return base64.urlsafe_b64encode(data).rstrip(b'=').decode('ascii')
def validate_fsc_token(token: str, client_cert_pem: bytes, group_id: str, manager_public_key) -> dict:
"""Valideer een FSC access token op de Inway."""
# 1. Decode token (signature verificatie met Manager's publieke sleutel)
payload = jwt.decode(token, manager_public_key, algorithms=["RS256", "PS256"])
# 2. Controleer group_id
if payload["gid"] != group_id:
raise ValueError(f"Verkeerde group_id: {payload['gid']}")
# 3. Controleer certificate binding (RFC 8705)
cert = load_pem_x509_certificate(client_cert_pem)
cert_thumbprint = base64url_encode(cert.fingerprint(SHA256()))
if payload["cnf"]["x5t#S256"] != cert_thumbprint:
raise ValueError("Certificate thumbprint komt niet overeen met token")
# 4. Controleer service bestaat
registered_services = ["service1", "service2"] # uit configuratie
if payload["svc"] not in registered_services:
raise ValueError(f"Service niet gevonden: {payload['svc']}")
# 5. Controleer expiratie (standaard JWT)
# jwt.decode doet dit automatisch met verify_exp=True
return payload
De Manager API (POST /contracts) verwacht content (verplicht) en signature (JWS string). De Manager berekent en valideert de signature op basis van het certificaat waarmee de mTLS-verbinding is opgezet.
# Stap 1: Contract indienen door consumer bij eigen Manager
# De Manager genereert de JWS-signature op basis van het mTLS-certificaat
curl -X POST https://consumer-manager.example.com:8443/v1/contracts \
--cert pkio_cert.pem --key pkio_key.pem --cacert pkio_ca_chain.pem \
-H "Content-Type: application/json" \
-H "Fsc-Manager-Address: https://consumer-manager.example.com:8443" \
-d '{
"content": {
"fsc_version": "1.0.0",
"iv": "06338364-8305-7b74-8000-de4963503139",
"group_id": "nl-overheid-productie",
"validity": {
"not_before": 1704067200,
"not_after": 1735689600
},
"grants": [{
"data": {
"type": "GRANT_TYPE_SERVICE_CONNECTION",
"service": {
"type": "SERVICE_TYPE_SERVICE",
"peer_id": "00000001234567890000",
"name": "brp-bevraging"
},
"outway": {
"peer_id": "00000001823288444000",
"identification": {
"type": "OUTWAY_IDENTIFICATION_TYPE_PUBLIC_KEY_THUMBPRINT",
"public_key_thumbprint": "3a56f2e9269ac63f0d4394c46b96539da1625b6a985d38029ff89f34e490960c"
}
}
}
}],
"hash_algorithm": "HASH_ALGORITHM_SHA3_512",
"created_at": 1704067200
},
"signature": "eyJhbGciOiJSUzUxMiIsIng1dCNTMjU2Ijo..."
}'
# Response: 201 Created
# Stap 2: Contract accepteren door provider via zijn Manager
# De accept endpoint vereist zowel content als signature (signatureRequest schema)
CONTRACT_HASH="<hash uit stap 1 response>"
curl -X PUT "https://provider-manager.example.com:8443/v1/contracts/${CONTRACT_HASH}/accept" \
--cert pkio_cert.pem --key pkio_key.pem --cacert pkio_ca_chain.pem \
-H "Content-Type: application/json" \
-H "Fsc-Manager-Address: https://provider-manager.example.com:8443" \
-d '{
"content": { <dezelfde contract content als in stap 1> },
"signature": "eyJhbGciOiJSUzUxMiIsIng1dCNTMjU2Ijo..."
}'
# Response: 201 Signature created
Let op: De
signature-waarden zijn JWS tokens (RFC7515) die een hash van de contract-content bevatten, ondertekend met het PKIoverheid-certificaat. De JWS payload bevatcontract_content_hash,type("accept"/"reject"/"revoke") ensigned_at. Het volledige algoritme staat in de fsc-core specificatie, sectie Signatures.
# Beschikbare services ophalen
curl -s https://directory.example.com:8443/services \
--cert pkio_cert.pem --key pkio_key.pem --cacert pkio_ca_chain.pem | jq
# Peers in de groep ophalen
curl -s https://directory.example.com:8443/peers \
--cert pkio_cert.pem --key pkio_key.pem --cacert pkio_ca_chain.pem | jq
# Specifieke service details
curl -s "https://directory.example.com:8443/services?name=brp-bevraging" \
--cert pkio_cert.pem --key pkio_key.pem --cacert pkio_ca_chain.pem | jq
| HTTP Status | Fsc-Error-Code | Beschrijving | Actie |
|---|---|---|---|
| 401 | ERROR_CODE_ACCESS_TOKEN_MISSING | Geen Fsc-Authorization header | Voeg token toe via Outway |
| 401 | ERROR_CODE_ACCESS_TOKEN_INVALID | Token handtekening ongeldig | Nieuw token ophalen |
| 401 | ERROR_CODE_ACCESS_TOKEN_EXPIRED | Token verlopen | Nieuw token ophalen |
| 403 | ERROR_CODE_WRONG_GROUP_ID_IN_TOKEN | Group ID in token matcht niet | Controleer groepsconfiguratie |
| 404 | ERROR_CODE_SERVICE_NOT_FOUND | Service niet geregistreerd | Controleer servicenaam in contract |
| 502 | ERROR_CODE_SERVICE_UNREACHABLE | Achterliggende service onbereikbaar | Probeer later opnieuw |
{
"code": "ERROR_CODE_ACCESS_TOKEN_EXPIRED",
"domain": "ERROR_DOMAIN_INWAY",
"message": "Token expired at 2024-01-15T10:30:00Z"
}
De Fsc-Error-Code header wordt altijd meegegeven bij foutresponses, naast de HTTP status code. Outway geeft 405 ERROR_CODE_METHOD_UNSUPPORTED bij een niet-ondersteunde CONNECT methode.
Zie reference.md voor componentarchitectuur, trust model, retry-strategie (exponential backoff), en protocoldocumentatie. Zie conflicts.md voor bronconflicten.