Beschrijving
In koppeltaal staat de FHIR resource service centraal. Applicaties, en niet personen, hebben toegang tot deze FHIR resource service. Zoals in het onderdeel Applicatierollen en rechten (TODO, link) beschreven, heeft elke applicatie een rol, die op zijn beurt gekoppeld zijn aan rechten. Om applicaties toegang te geven tot de FHIR resource service dient deze eerst geauthenticeerd te worden. Het proces van vaststellen of de applicatie is wie die zegt dat hij is. Daarna vindt autorisatie plaats, vaststellen of de applicatie de rechten heeft de vraag of opdracht uit te voeren. Dit onderdeel heeft enkel betrekking op de authenticiteit van de applicatie. De autorisatie gebeurt door middel van de rollen en rechten beschreven in Applicatierollen en rechten (TODO, link).
De authenticatie van de applicatie wordt gerealiseerd door middel van het SMART on FHIR backend services protocol. Dit protocol is gebaseerd op OAuth2 client credential flow. Deze SMART on FHIR backend services maakt gebruik van JWT gebaseerde client credentials (rfc7523). Deze vorm van authenticatie gaat uit van een manier van authenticeren gebaseerd op een publieke / private key combinatie.
Overwegingen
SMART on FHIR backend services
In koppeltaal krijgen niet personen maar applicaties toegang tot de FHIR resource service. In het koppeltaal domein wordt door middel van rollen vastgelegd welke rechten een applicatie heeft. De SMART on FHIR backend services beschrijft hoe een applicatie toegang krijgt tot de FHIR resource service, en vervult hiermee een groot deel van het toegang krijgen. Waar koppeltaal meer specifiek wordt dan SMART on FHIR backend services is op het gebied van de rechten die de applicatie krijgt. Deze worden vastgelegd in het access_token door middel van door Koppeltaal beschreven autorisatieregels, zodat er een koppelvlak ontstaat tussen autorisatie service en FHIR resource service.
JSON Web Token (JWT) Profile for OAuth 2.0 Client Authentication (rfc7523)
In eerdere versies van koppeltaal worden de credentials door middel van een client_id en een geheime sleutel gerealiseerd. In SMART on FHIR backend services wordt gebruik gemaakt van JWT Profile for OAuth 2.0 Client Authentication rfc7523. In dit profiel wordt een JWT token als bewijs van de client authenticatie (client assertion) meegegeven. De authenticatie gebeurt niet op basis van een geheime sleutel, maar op een sleutelpaar. Dit sleutelpaar wordt door de cliënt gegenereerd. Het sleutelpaar bestaat uit twee delen; een geheim deel en een publiek deel. Een dergelijk sleutelpaar wordt een public / private key genoemd. De publieke sleutel kan enkel gebruikt worden om een ondertekend bericht mee te controleren, enkel de geheime sleutel kan gebruikt worden om te ondertekenen. Zo wordt dit in rfc7523 ook toegepast. De geheime sleutel wordt gebruikt om het JWT token te ondertekenen, de publieke sleutel om deze te validereren. Dit heeft een aantal grote voordelen ten opzichte van een gedeeld geheim. In het geval van een gedeeld geheim moet het geheim altijd gedeeld worden, met het risico van mogelijk verlies van het geheim. In sleutelpaar kan gemakkelijk gegenereerd worden en het geheime deel hoeft nooit de omgeving te verlaten. Verder is het mogelijk meerdere actieve sleutelparen te hebben, zodat indien een omgeving uit meerdere instanties bestaat, iedere instantie een eigen sleutelpaar kan genereren. Omdat bij de publicatie van de publieke sleutels gebruik gemaakt wordt van JWKS (TODO, link invoegen), kunnen sleutels gemakkelijk en snel vernieuwd worden.
Het access_token
Het access_token bestaat net als de client credentials uit een JWT token. Hoe dit access_token er uit ziet ligt vast in koppeltaal. Door de inhoud en structuur van het access_token vast te leggen ontstaat een ontkoppeling van componenten. Een willekeurige FHIR service kan, indien deze de autorisatie service vertrouwt, de inhoud van het token direct vertalen naar toepasbare restricties. Hiervoor is een syntax vastgelegd die de permissie vertaald naar een waarde van het scope veld. De keuze voor de standaardisatie van het token is voor de schaalbaarheid van Koppeltaal van belang.
De scope in het token request
Omdat de applicaties in het koppeltaaldomein toegewezen rollen hebben, die naar permissies vertaald worden en in het scope veld het access_token, is er voor de scope in de aanvraag voor een token voor een applicatie lastig te vullen. Om deze reden is het toegestaan de waarde * te gebruiken voor het verkrijgen van alle toegestane permissies in de aangevraagde scope. Indien de applicatie om enkel een specifieke scope vraagt, mag de autorisatie service de uitgegeven scope hiermee beperken.
Toepassing, restricties en eisen
SMART on FHIR backend services toegelicht
De SMART on FHIR backend services bestaat uit een OAuth 2 client authentication flow met als methode voor client authentication rfc7523. Deze standaard wordt beschreven in de bestaande documentatie. De OAuth 2 client authentication flow bestaat uit een enkel request naar de authorization service waar op basis van de identiteit van de cliënt een access_token wordt toegekend. Er wordt door SMART on FHIR backend services geen gebruik gemaakt van een refresh_token, dit is gebruikelijk bij de OAuth 2 client authentication flow, omdat de access_token eenvoudig en geautomatiseerd opnieuw aangevraagd kan worden. Koppeltaal bepaald verder dat de levensspanne van het access_token kort is, dit om wijzigingen in rechten en permissies snel te kunnen propageren. Hoe de client zich authenticeert wordt in het volgende onderdeel besproken.
De aanvraag, het antwoord, en de bearer token.
De aanvraag van de applicatie naar de autorisatie service bevat de volgende parameters:
- scope: een lijst van scopes volgens het permissie schema of * voor alle beschikbare permissies.
- grant_type: altijd client_credentials
- client_assertion_type: altijd urn:ietf:params:oauth:client-assertion-type:jwt-bearer
- client_assertion: het gegenereerd JWT token van de applicatie.
Het antwoord kent de volgende velden:
- access_token, de JWT die gebruikt kan worden om de FHIR resource server mee te benaderen.
- token_type: altijd ‘bearer’
- expires_in: de tijdspanne van het token, maximaal 5 minuten. Deze komt overeen met het iat veld van de JWT access_token.
- scope: de toegewezen permissies, deze zijn identiek aan de waarde van het scope veld van het JWT access_token.
De REST API van de FHIR server kan met de access_token benaderd worden. Dit kan maximaal binnen de tijdspanne van de expires_in waarde. Indien het token verlopen is, moet er een nieuw token aangevraagd worden. Het access token wordt meegegeven in de Authorization header, en wordt voorafgegaan met de waarde Bearer.
JWT Profile for OAuth 2.0 Client Authentication (rfc7523)
Dit profiel boven op OAuth 2 bepaalt dat de cliënt, de applicatie in het geval van koppeltaal, zich identificeert met een JWT token. Dit doet de applicatie door een JWT token te ondertekenen met zijn eigen gegenereerde sleutelpaar. Het ondertekende JWT token bevat als issuer (iss) veld de client_id van de applicatie. De authorization service moet het token valideren door middel van de publieke sleutel die is gekoppeld aan de cliënt. Deze publieke sleutel wordt door middel van een JWKS url beschikbaar gesteld. De velden in het JWT token zijn als volgt gemapped:
- iss: de client_id van de applicatie
- sub: de client_id van de applicatie
- aud: de URL van het token_endpoint
- iat: timestamp van uitgifte
- jti: unieke waarde per token
- exp: geldig tot timestamp, maximaal 5 minuten
De header van het token moet de key id (kid) van het gebruikte sleutelpaar bevatten.
JWKS
JWKS is geen eigen standaard, maar komt voor in de OAuth 2.0 Authorization Server Metadata standaard als optionele URL. In de praktijk is het pad <base_url>/.well-known/jwks.json en bevat het document een JSON structuur met in de “keys” waarde een lijst van JSON Web Keys (rfc7517). In koppeltaal wordt deze URL gebruikt door de autorisatie service en optioneel de applicaties, zowel module als portal, om de public keys bekend te maken.
Het access_token, de scope en de permissie syntax.
Het access_token vervult een speciale rol in koppeltaal. Het token bevat de rechten van de applicatie op basis van de toegewezen rollen. Het access_token wordt ondertekend door de autorisatie service, die op zijn beurt de publieke sleutel bekend maakt via een JWKS url. Verder wordt de waarde van het azp veld gevuld met de client_id van de applicatie. De velden in het access token zijn als volgt gemapped:
- iss: de base url van de autorisatie service
- azp: de client_id van de applicatie die toegang krijgt
- iat: timestamp van uitgifte
- exp: geldig tot timestamp, maximaal 5 minuten
- scope: de lijst van toegestane permissies, elementen in de lijst worden onderscheiden door een spatie.
De scope bestaat uit een lijst van permissies. Zoals eerder aangegeven kent een permissie een lijst van devices op basis van de scope, resource en actie. De scope kan zijn, alle devices (ALL), alleen je eigen device (OWN) of een selectie aan devices (GRANTED). Een permissie is opgebouwd uit de volgende formaat:
<device(s)>/<resource(s)>.<actie(s)>
De scope is case-sensitive. Let op dat de resource altijd middels PascalCase gezet is. De actie dient altijd in lower-case meegegeven te worden.
Devices en permissies
Zoals beschreven kan de scope ALL, OWN of GRANTED zijn. In het perrmissiefomaat wordt de volgende mapping gemaakt:
- ALL: *
- OWN: de eigen device logical id
- GRANTED: een lijst van device logical ids, gescheiden door een komma.
Resource
De resource wordt gevuld met het resource type, bijvoorbeeld ‘Patient’. Er mag een wildcard ‘*’ gebruikt worden om alle resources aan te duiden. De resource MOET altijd als PascalCase gezet worden.
Actie
De actie MOET één van de volgende twee opties zijn:
- Minimaal één van de onderstaande letters. De letters MOETEN lower-case zijn en hebben GEEN vaste volgorde.
- 'c' (Create),
- 'r' (Read),
- 'u' (Update),
- 'd' (Delete)
- ‘*’ (wildcard, ALL)
Scope voorbeelden
Scope | Beschrijving |
---|---|
13,20/ActivityDefinition.r | ActivityDefinitions met resource-owner Device/13 en Device/20 mogen gelezen worden |
*/Task.dru | Mag Tasks van alle Devices deleten, lezen en updaten |
13/*.r | Alle resources met resource-owner Device/13 mogen gelezen worden |
17/Patient.* | Mag alle acties op Patient resources uitvoeren met resource-owner Device/17 |
*/*.r | Mag alle resources van alle devices lezen |
*/*.* | Mag alle acties op alle resources van alle devices uitvoeren |
Het verkrijgen van toegang in stappen
Voordat de token aangevraagd kan worden gaan we er vanuit dat de volgende situatie van toepassing is:
- De applicatie is aangemeld in het domein en heeft zijn public key bekend gemaakt door een JWKS url of door een directe registratie van de public key.
- In de FHIR resource service moet een Device zijn aangemaakt waarin de client_id overeenkomt met de applicatie.
De algemene SMART on FHIR backend services flow ziet er als volgt uit:
- De module applicatie haalt het smart configuratie op bij de FHIR service (.well-known/smart-configuration). Dit statement bevat de volgende endpoints:
- token_endpoint
- De applicatie genereert een JWT token op basis van de private key met de volgende velden:
- iss: de client_id van de applicatie
- sub: de client_id van de applicatie
- aud: de URL van het token_endpoint
- iat: de timestamp now()
- jti: een unieke waarde voor deze aanvraag, UUIDs zijn hiervoor geschikt.
- exp: de timestamp now() + 5 minuten
- De applicatie benadert het token_endpoint endpoint met de volgende parameters in een application/x-www-form-urlencoded POST request:
- scope
- grant_type: `client_credentials`
- client_assertion_type: `urn:ietf:params:oauth:client-assertion-type:jwt-bearer`
- client_assertion: het gegenereerde JWT token uit de vorige stap.
- De autorisatie service ontvangt het token, daarbij onderneemt hij de volgende stappen:
- Op basis van de domeinconfiguratie wordt de juiste JWKS URL opgehaald om de token te valideren.
- De token wordt gevalideerd op basis van de kid en de keys beschikbaar in de JWKS URL.
- De client_id wordt gekoppeld aan de juiste Device in de FHIR resource service.
- In de domeinconfiguratie wordt de juiste rol(en) opgehaald, deze resulteert in een lijst van permissies.
- De autorisatie service genereert een access_token met de volgende waarden:
- iss: de base url van de autorisatie service
- azp: de client_id
- iat: timestamp now()
- exp: timestamp now() + 5 min
- scope: de lijst van permissies, geformatteerd volgens het door koppeltaal vastgelegde formaat.
- De autorisatie service geeft het volgende JSON geformateerde antwoord:
- access_token, het JWT token uit de vorige stap.
- token_type: altijd ‘bearer’
- expires_in: idem als de iat uit de JWT
- scope: idem als de scope uit de JWT
- De applicatie kan nu de FHIR resource service benaderen door gebruik te maken van de access_token. De access token wordt in het request in de Authorization header meegegeven in het volgende formaat:
Authorization: Bearer {{access_token}} - De FHIR resource service ontvangt de access_token en valideert deze door middel van de JWKS URL van de autorisatie service. Deze is in de .well-known/smart-configuration van de FHIR resource service beschikbaar.
- De FHIR resource service interpreteert de permissies zoals deze in de scope zijn weergegeven en beperkt de toegang van de applicatie op basis van deze permissies.
Voorbeelden
[code]