Koppelmij Implementation Guide
0.1.0 - ci-build
Koppelmij Implementation Guide - Local Development build (v0.1.0) built by the FHIR (HL7® FHIR® Standard) Build Tools. See the Directory of published versions
Dit document analyseert een fundamentele spanning in de architectuur van KoppelMij: de rol van de DVA als authorization server versus identity provider, en de implicaties voor het gebruik van de openid fhirUser scope in SMART on FHIR launches naar modules.
Het vraagstuk ontstaat door de eis uit de startnotitie dat modules via meerdere wegen toegankelijk moeten zijn (meervoudige toegang), en de daaruit volgende noodzaak voor consistente gebruikersidentificatie over deze verschillende toegangswegen.
In OAuth 2.0 en OpenID Connect is er een belangrijk onderscheid:
Authorization Server (OAuth 2.0):
access_token uit voor resource toegangIdentity Provider (OpenID Connect):
id_token uit (JWT met gebruikersclaims)openid scope in de authorization requestSMART on FHIR gebruikt openid fhirUser voor gebruikersidentificatie:
openid fhirUser scope:
id_token uitgeeftsub (subject identifier), name, email, etc."fhirUser": "Patient/123" of "fhirUser": "Practitioner/456"Belangrijk: In SMART on FHIR v2 is fhirUser een sub-scope van openid. De fhirUser scope kan niet los worden gebruikt zonder openid. De combinatie openid fhirUser is daarom altijd vereist wanneer gebruikersidentiteit nodig is.
Wanneer een PGO taken verzamelt bij de DVA:
Geen openid scope:
scope=patient/Task.read patient/ActivityDefinition.read
Reden:
access_token uit (geen id_token)Rol DVA:
{
"access_token": "eyJhbG...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "patient/Task.read patient/ActivityDefinition.read",
"patient": "Patient/123"
}
Geen id_token aanwezig - DVA is geen IdP in deze context.
Wanneer een portaal (behandelportaal of patiëntportaal) een module start in Koppeltaal:
Inclusief openid fhirUser:
scope=launch openid fhirUser patient/*.read
Reden:
Rol Koppeltaal Authorization Server:
{
"access_token": "eyJhbG...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "launch openid fhirUser patient/*.read",
"id_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"patient": "Patient/123",
"fhirUser": "Patient/123"
}
Id_token aanwezig - bevat sub claim met unieke gebruikers identifier.
Voorbeeld id_token payload:
{
"iss": "https://koppeltaal.example.com",
"sub": "user-unique-id-12345",
"aud": "module-client-id",
"exp": 1234567890,
"iat": 1234567000,
"fhirUser": "Patient/123"
}
openid fhirUser scopeUit de Koppeltaal specificatie (TOP-KT-007) blijkt een duidelijke rationale voor het gebruik van openid fhirUser scope:
1. Unsolicited authentication risico:
"HTI is daarom, zonder extra identificatie van de gebruiker, onvoldoende beveiligd om toegang te verlenen tot persoonlijke en/of medische gegevens."
Het HTI token alleen (met context informatie) is onvoldoende beveiligd. Er is risico dat de launch wordt onderschept of gestolen. Daarom is een authenticatiestap noodzakelijk.
2. SMART on FHIR autorisatiestap als beveiliging:
"Dit is dan ook de rol van SMART on FHIR app launch framework. Hier wordt in de autorisatiestap de gebruiker door middel van SSO opnieuw geïdentificeerd, en deze gegevens worden gematched met de inhoud van het HTI launch bericht."
In de /authorize stap van SMART on FHIR wordt de gebruiker via SSO opnieuw geïdentificeerd. Deze identiteit wordt gematched met de sub claim in het HTI token.
3. Identity matching proces:
Het HTI token bevat in het sub veld een FHIR resource referentie (Patient/123, Practitioner/456, of RelatedPerson/789). Bij de autorisatiestap:
4. Scope is altijd launch openid fhirUser:
"Omdat in koppeltaal vooralsnog de gebruiker altijd onderdeel is van de launch, is de scope altijd
launch openid fhirUser."
Koppeltaal vereist altijd gebruikersidentificatie voor module launches die persoons/medische gegevens verwerken.
5. Access_token is NOOP:
"In koppeltaal is de keuze gemaakt om de applicaties - en niet de individuele gebruikers - toegang te geven op de FHIR resource server."
Het access_token is altijd "NOOP" in Koppeltaal. Toegang tot de FHIR resource service gebeurt op applicatie-niveau via SMART Backend Services (RFC 7523). Maar het id_token en de context parameters zijn wel van toepassing.
6. Context in token response: Naast standaard OAuth/OIDC velden bevat de token response ook FHIR context:
resource: Task reference (Task/123)definition: ActivityDefinition referencesub: Gebruiker die launch uitvoert (Patient/Practitioner/RelatedPerson)patient: Optioneel, als sub niet de patiënt isintent: Optioneel, voor verschillende launch scenario'sfhirUser: FHIR resource referentie van gebruikerKernpunt:
Koppeltaal gebruikt
openid fhirUserscope specifiek omdat modules persoons- en/of medische gegevens verwerken, en daarom sterke gebruikersidentificatie noodzakelijk is. Het id_token metsubclaim is de cryptografische garantie van deze identiteit.
Wanneer een PGO een module start via de DVA:
Uit de startnotitie:
"Een client/patient moet zelf kunnen kiezen of hij de taak voor een module wil starten via het clienten/patientenportaal of via de PGO."
Consequentie:
Zonder gebruikersauthenticatie (zoals bij PGO verzamelen):
openid fhirUser scopeaccess_token voor resource toegangid_token en geen fhirUser in responseMet gebruikersauthenticatie (openid fhirUser scope zoals Koppeltaal):
openid fhirUser scope aanid_token met sub claimfhirUser referentie in token responseopenid fhirUser nodig is voor modulesHet primaire probleem: Module applicaties verwerken persoonlijke gezondheidsgegevens. Deze gegevens kunnen al bestaan van eerdere sessies (bijvoorbeeld via een portaal). Wanneer de module nu gestart wordt vanuit een PGO, moet de module zeker weten dat de persoon achter het toetsenbord voldoende geauthenticeerd is om toegang te krijgen tot die bestaande zorggegevens.
Scenario zonder gebruikersauthenticatie (onvoldoende voor meervoudige toegang):
openid fhirUser)
scope=launch openid fhirUser patient/*.readid_token met sub=user-12345 + fhirUser=Patient/123sub)scope=launch patient/*.read (geen openid fhirUser)access_token, geen id_token, geen fhirUserScenario met gebruikersauthenticatie (voldoende voor meervoudige toegang):
openid fhirUser)
scope=launch openid fhirUser patient/*.readid_token met sub=user-12345 + fhirUser=Patient/123openid fhirUser)
scope=launch openid fhirUser patient/*.readid_token met sub=user-12345 + fhirUser=Patient/123 van DVAsub claimParallel met Koppeltaal's "unsolicited authentication" probleem: Precies zoals Koppeltaal concludeerde:
"HTI is daarom, zonder extra identificatie van de gebruiker, onvoldoende beveiligd om toegang te verlenen tot persoonlijke en/of medische gegevens."
Voor KoppelMij geldt hetzelfde: zonder gebruikersauthenticatie (en dus zonder openid fhirUser scope, zonder id_token, en zonder fhirUser referentie) is de launch onvoldoende beveiligd om toegang te geven tot persoonlijke en/of medische gegevens, en kan de module de gebruiker niet herkennen over verschillende toegangswegen.
Eenvoudig gesteld:
Omdat de module ook via andere manieren te benaderen is (zoals via Koppeltaal portalen), moet de module een geautoriseerde gebruiker hebben in de context van een PGO (KoppelMij).
Conclusie uit analyse:
Omdat modules via meerdere wegen toegankelijk moeten zijn, en deze modules gezondheidsgegevens verwerken die gebonden zijn aan een persoon, MOET de module de identiteit van de gebruiker kunnen vaststellen als dezelfde persoon over deze verschillende toegangswegen.
Parallel met Koppeltaal: Precies zoals Koppeltaal concludeerde dat HTI token alleen onvoldoende is zonder gebruikersidentificatie, geldt hetzelfde voor KoppelMij:
openid fhirUser → id_token met sub voor gebruikersidentificatieopenid fhirUser → id_token met sub voor gebruikersidentificatieIn beide gevallen is de sub claim in het id_token de enige manier waarop de module dezelfde gebruiker kan herkennen over verschillende toegangswegen.
Dit betekent:
sub claim) provisionensub waarde moet stabiel en consistent zijn over tijdDe sub waarde:
De DVA heeft verschillende rollen afhankelijk van de context:
| Context | Rol DVA | openid fhirUser scope |
id_token + fhirUser |
|---|---|---|---|
| PGO verzamelt taken | Authorization Server | Nee | Nee |
| Module launch met gebruikersauthenticatie | Authorization Server + Identity Provider | Ja | Ja |
| Module launch zonder gebruikersauthenticatie | Authorization Server | Nee | Nee |
1. Identity Provider functionaliteit:
sub identifiers genereren/beheren2. Consistente gebruikersidentificatie:
sub waarde krijgensub waarde moet stabiel zijn over tijd en over launchessub waarde moet consistent zijn met andere systemen (indien van toepassing)3. Privacy overwegingen:
sub claim hoeft niet BSN te zijn (pseudonimisering)SMART on FHIR launch scope voor module:
scope=launch openid fhirUser patient/*.read
DVA authorize endpoint:
GET /authorize?
response_type=code&
client_id=module-123&
redirect_uri=https://module.example.com/callback&
scope=launch+openid+fhirUser+patient/*.read&
state=xyz&
launch=launch-token-abc
DVA token response:
{
"access_token": "eyJhbG...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "launch openid fhirUser patient/*.read",
"id_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"patient": "Patient/123",
"fhirUser": "Patient/123"
}
Id_token payload:
{
"iss": "https://dva.example.com",
"sub": "pseudonym-user-abc123",
"aud": "module-123",
"exp": 1234567890,
"iat": 1234567000,
"auth_time": 1234567000,
"fhirUser": "Patient/123"
}
In het document koppelmij_option_3a.md staat op regel 115-125:
DVA als SMART on FHIR Authorization Server (niet OIDC)
Belangrijk onderscheid:
- DVA is een SMART on FHIR authorization server, niet een OpenID Connect provider
- Reden: DVA verificeert de gebruiker via browser sessie en eventueel DigID, maar geeft geen id_token uit
- Gevolg: Module ontvangt geen
id_tokenen dus geensubclaim voor gebruikersidentificatie
Deze analyse toont aan dat deze keuze heroverwogen moet worden:
De redenering in Optie 3a was:
fhirUser geeft voldoende informatieMaar de analyse in dit document toont:
fhirUser alleen is onvoldoende voor identificatie over verschillende contextensub claim is noodzakelijkDVA implementeert volledige OpenID Connect:
sub claimscope=launch openid fhirUser patient/*.readVoordelen:
Nadelen:
PGO blijft Identity Provider, DVA federeert:
sub (of gemapped)Voordelen:
Nadelen:
sub in access_token claims (zonder volledige OIDC)DVA geeft geen id_token, maar sub in access_token:
sub claimopenid fhirUser scope maar DVA geeft geen id_token uitsub uit access_token JWT halenVoordelen:
sub wel beschikbaar voor identificatieNadelen:
DVA geeft custom parameter in token response:
"user_identifier": "pseudonym-abc123"Voordelen:
Nadelen:
Rationale:
Implementatie:
scope=launch openid fhirUser patient/*.readsub claimsub is pseudoniem (niet BSN) voor privacyMigratiepad:
openid fhirUser scope met id_token te vereisenAls volledige OIDC implementatie niet haalbaar is:
sub claimsub extraheren uit access_tokenTrade-off:
koppelmij_option_3a.mdSectie "DVA als SMART on FHIR Authorization Server (niet OIDC)" moet worden herzien:
Oud:
DVA is een SMART on FHIR authorization server, niet een OpenID Connect provider Module ontvangt geen
id_tokenen dus geensubclaim voor gebruikersidentificatie
Nieuw:
DVA is zowel een SMART on FHIR authorization server als een OpenID Connect provider Module gebruikt
openid fhirUserscope voor volledige gebruikersidentificatie Id_token bevatsubclaim met pseudoniem voor consistente identificatie over toegangswegen
Oud (regel 78-82):
scope=launch+openid+fhirUser+patient/*.read
(zonder id_token in response)
Nieuw:
scope=launch+openid+fhirUser+patient/*.read
(met id_token in response)
Toevoegen aan token response (regel 95-102):
{
"access_token": "...",
"token_type": "Bearer",
"expires_in": 3600,
"id_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"patient": "Patient/123",
"fhirUser": "Patient/123"
}
Er is besloten dat de keuze voor het gebruik van openid fhirUser scope (en daarmee het uitgeven van een id_token) afhangt van de bilaterale afspraak tussen de moduleleverancier en de zorgaanbieder.
Kernpunten van de beslissing:
openid fhirUser worden gebruikt, omdat modules doorgaans via meerdere wegen toegankelijk zijn en gebruikersidentificatie noodzakelijk isid_token) zijn toegestaan, valt wel binnen het stelselRationale:
Deze beslissing erkent dat:
id_token afhangt van de specifieke use case en architectuurPraktische implicatie:
id_token en fhirUser uit te geven wanneer openid fhirUser scope wordt aangevraagdopenid fhirUser scope, geen id_token, geen fhirUser) wanneer dit bilateraal is afgesprokenfhirUser te krijgen zonder openid scope en id_token - dit zijn altijd gekoppeld in SMART on FHIR v2De analyse in dit document toont aan dat voor modules met meervoudige toegang (via PGO én via portalen) het gebruik van openid fhirUser scope met id_token noodzakelijk is voor consistente gebruikersidentificatie. Dit komt overeen met de rationale van Koppeltaal (TOP-KT-007).
De beslissing om de specifieke scope-keuze als bilaterale afspraak te positioneren biedt flexibiliteit, terwijl de verwachting is dat in de praktijk openid fhirUser de standaard zal zijn voor de meeste implementaties.