| name | ls-fsc |
| description | Federated Service Connectivity (FSC, voorheen NLX): inway, outway, service directory, peers, external contracts, Manager API. |
| model | sonnet |
| allowed-tools | ["Bash(gh api *)","Bash(gh issue list *)","Bash(gh pr list *)","Bash(gh search *)","Bash(curl -s *)","WebFetch(*)"] |
| metadata | {"created-with-ai":"true","created-with-model":"claude-opus-4-20250514","created-date":"2025-02-12","status":"concept"} |
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.
Federated Service Connectivity (FSC)
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.
Versiemodel
Net als andere Logius-standaarden kent FSC twee publicatiekanalen (vergelijkbaar met W3C):
- Vastgestelde versie (DEF): officieel goedgekeurd, gepubliceerd op
gitdocumentatie.logius.nl
- Werkversie (draft): werk-in-uitvoering, gepubliceerd op
logius-standaarden.github.io
FSC 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.
Repositories
¹ CV = consultatieversie, nog niet definitief vastgesteld.
Service Connectivity Flow
Het proces waarmee een consumer-organisatie een service van een provider-organisatie afneemt verloopt in de volgende stappen:
Stap 1: Contract aanmaken
De consumer maakt een Contract aan met daarin een ServiceConnectionGrant. Dit grant specificeert welke service de consumer wil afnemen en bij welke provider.
Stap 2: Contract ondertekenen door consumer
De consumer ondertekent het contract cryptografisch met zijn eigen certificaat. De handtekening bevat de certificate thumbprint (SHA-256).
Stap 3: Contract verzenden naar provider
Het ondertekende contract wordt via de Manager API (POST /contracts) naar de Manager van de provider gestuurd.
Stap 4: Contract ondertekenen door provider
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.
Stap 5: Access token verkrijgen
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_credentials
scope: de hash van het specifieke Grant dat de consumer wil gebruiken
client_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.
Stap 6: Verzoek via Outway naar Inway
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>
Stap 7: Validatie en doorsturen door Inway
De Inway van de provider:
- Extraheert het JWT uit de
Fsc-Authorization header
- Valideert de handtekening van het token
- Controleert de claims (
aud, svc, gth, gid, cnf)
- Verifieert dat het certificaat van de verbinding overeenkomt met de
cnf thumbprint
- Stuurt het verzoek door naar de achterliggende service
- Retourneert de response aan de Outway
Contract & Grant Types
Contract JSON-structuur
Een 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: service kan ook SERVICE_TYPE_DELEGATED_SERVICE zijn (extra veld delegator). outway.identification kan ook OUTWAY_IDENTIFICATION_TYPE_DOMAIN_NAME zijn (veld domain_name). Grants kunnen optioneel een properties-object bevatten. Zie de fsc-core OpenAPI spec voor het volledige schema.
Grant Types
| 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 |
Access Token (JWT)
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
}
Manager API Endpoints
De Manager biedt de volgende REST API endpoints:
Peer-beheer
| 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. |
Contract-beheer
| 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. |
Token-uitgifte
| 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). |
Service-discovery
| Methode | Endpoint | Beschrijving |
|---|
GET | /services | Lijst van alle gepubliceerde services (beschikbaar op de Directory). |
Netwerkconfiguratie
Poorten
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. |
HTTP-vereisten
- HTTP/1.1 is verplicht als minimale versie voor alle FSC-verbindingen
- HTTP/2 is optioneel en mag worden ondersteund als aanvulling
- TLS is verplicht; de minimale TLS-versie wordt bepaald door de Group Rules (in de praktijk minimaal TLS 1.2)
- mTLS is verplicht: zowel client als server presenteren een certificaat
Logging Extensie
De FSC Logging extensie (fsc-logging) beschrijft hoe transactielogs worden vastgelegd voor audit- en verantwoordingsdoeleinden.
Transactie-ID
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
Logvelden
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 |
Logging per component
- Outway: logt uitgaande verzoeken met
direction: out, het gebruikte grant en de bestemming
- Inway: logt inkomende verzoeken met
direction: in, het gevalideerde grant en de bron
- Beide componenten loggen dezelfde
transaction_id, waardoor correlatie mogelijk is
Implementatievoorbeelden
Outway Proxy (Python/FastAPI)
from 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: 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)
inway_url = resolve_inway_url(service_name)
access_token = get_access_token(grant_hash, "mijn-oin")
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()),
**{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),
)
Inway Token Validatie (Python)
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."""
payload = jwt.decode(token, manager_public_key, algorithms=["RS256", "PS256"])
if payload["gid"] != group_id:
raise ValueError(f"Verkeerde group_id: {payload['gid']}")
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")
registered_services = ["service1", "service2"]
if payload["svc"] not in registered_services:
raise ValueError(f"Service niet gevonden: {payload['svc']}")
return payload
Contract Aanmaken en Ondertekenen (curl)
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.
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..."
}'
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..."
}'
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 bevat contract_content_hash, type ("accept"/"reject"/"revoke") en signed_at. Het volledige algoritme staat in de fsc-core specificatie, sectie Signatures.
Service Discovery via Directory (curl)
curl -s https://directory.example.com:8443/services \
--cert pkio_cert.pem --key pkio_key.pem --cacert pkio_ca_chain.pem | jq
curl -s https://directory.example.com:8443/peers \
--cert pkio_cert.pem --key pkio_key.pem --cacert pkio_ca_chain.pem | jq
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
Foutafhandeling
Inway Error Codes
| 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 |
Error Response Formaat
{
"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.
Achtergrondinfo
Zie reference.md voor componentarchitectuur, trust model, retry-strategie (exponential backoff), en protocoldocumentatie. Zie conflicts.md voor bronconflicten.