Sequence diagrams and examples - Integrating EHR with the SFM client
The EHR system must implement support to request HelseID for access tokens with audiences to SFM and a function to renew tokens in order to support single-sign-on for local login, use of SFM and communication with underlying services.
The sequence diagram and accompanying descriptions documents the process of integrating external EPJ systems with the SFM.
Sequence Diagram
Authentication
Authentication is required against the Norwegian IDP for proper identification of the SFM.
After authenticating using OpenId you should be redirected back to the EPJ and receive an access_token that represents the selected organization in the SFM.
Creating an SFM session
After having received a valid access_token
a call needs to be made to the SFM Service Gateway in order to create a new SFM session and the response will contain the portal addresses you can use.
Below is an example of an HTTP POST request that creates a session.
POST {{SfmSessionGatewayEndpoint}}/api/Session/create
Authorization: Bearer {{access_token}}
ContentType: application/json
{
"nonce": "{{nonceHashBase64}}"
}
Variable | Meaning |
---|---|
{{SfmSessionGatewayEndpoint}} |
Base address of Sfm Session Gateway endpoint |
{{access_token}} |
Access token returned from Authentication against the Norwegian IDP |
{{nonceHashBase64}} |
The hash of a nonce base64 randomly generated by the EPJ system. To generate a nonce see the section below “Generating the nonce" |
The response to this call will contain the newly created session in the following format (example):
{
"id": "6zDBG7i0s6BAiMJMQuVptH1/cPpSZwJcbgUPN+6ZhhCZqJ6xe+z1hTu6zrO4lI5Rm8fJUOSaqYYpmCo9RDySmw==",
"code": "YFGDdogyzWCart328M0y5wfeG17QKFQs3AsQb4oz7Aud3AGbs6IOmo62+5ZS00UaUnjAaaBoilLNxkbKk/RIlw==",
"apiAddress": "http://localhost:5010/",
"metadata": {
"patientportal": "http://localhost:4200",
"enterpriseportal": "http://localhost:4203",
"healthcareportal": "http://localhost:4201"
}
}
Variable | Meaning |
---|---|
´Id´ | Id generated by SFM |
´Code´ | Code that identifies the session in SFM, limited use: only once, valid at least 15 s |
´ApiAddress´ | A valid address for the SFM Server for Portals to connect to |
´Metadata´ | This represents a dictionary of metadata that supports the following values: patientportal→ for the Patient Portal, enterpriseportal→ for the Enterprise Portal, healthcareportal→ for the Healthcare Portal |
Refreshing the session
To maintain a contiuous user experience the EHR system must provide a refresh scheme towards HelseID, and provide SFM with the renewed token before the previous token expires.
With the new token in the Authorization header the EHR does a POST with empty body:
POST {{SfmSessionGatewayEndpoint}}/api/Session/refresh
Authorization: Bearer {{access_token}}
Ending the session
To end a user session against SFM, the EPJ host system should end/terminate the SFM session. Normally this will follow a logout in the EPJ/EHR system.
After terminating a session an attempt to refresh will fail. The SFM client should also be closed, as further work will not be possible.
POST {{SfmSessionGatewayEndpoint}}/api/Session/end
Authorization: Bearer {{access_token}}
Opening each Portal
Each SFM Portal/client needs to be initialized in an Iframe in order to load into the SFM client using a returned Portal address.
Examples of Iframes loading the Patient portal and enterprise portal, respectively:
<iframe id="sfmclientFrame" src="https://client.dev.sfm.cloud"></iframe>
<iframe id="sfmclientFrame" src="https://enterprise-portal.dev.sfm.cloud"></iframe>
In order to load each of the clients correctly, they need to login against the SFM server.
First you need to add an event listener to handle the messages coming from the SFM client, for example:
// setup listener to receive messages from the SFM Client
window.addEventListener("message", receiveMessage);
On the method that processes the messages returned from the SFM we should implement the login action.
In order to trigger the login event to a client you should wait for the clientLoaded event and trigger the login event using iframe.contentWindow.postMessage(msg, '*') with a message in a javascript object. Note specifically that the contentWindow property for the iframe is used here.
Example code:
function receiveMessage(event) {
console.debug('Received new message from sfm client');
if(event.data != undefined){
if (event.data.clientLoaded == 'success'){
console.debug('Start sfm login on sfm client...');
let sessionCode = decodeURIComponent('{{SessionCode}}');
let sessionNonce = decodeURIComponent('{{SessionNonce}}');
let msg={'action': 'login', 'code' : sessionCode, 'nonce': sessionNonce, 'apiEndpoint': 'https://server.dev.sfm.cloud/'};
iframe = document.getElementById('sfmclientFrame');
console.debug('EPJ sending sfm login post message...');
iframe.contentWindow.postMessage(msg, '*');
}
}
}
Variable | Meaning |
---|---|
apiEndpoint |
SFM server address |
sessionCode |
Code returned from the create action. |
sessionNonce` | Nonce generated by EPJ system on base64 - to generate nonce see session bellow “Generating the nonce” |
Opening patient context portals with a Patient ticket
The PatientPortal and DisplayPortal is showing patient information.
Before EHR can open the portal/client a patientTicketId must be retreived by a separate API action where the patient identity is input: (The pid may be a national ID or xxx-id from the EHR system)
POST {{SfmSessionGatewayEndpoint}}/api/PatientTicket
Authorization: Bearer {{access_token}}
ContentType: application/json
{
"patientPid": "{{patientPid}}"
}
Variable | Meaning |
---|---|
{{SfmSessionGatewayEndpoint}} |
Base address of Sfm Session Gateway endpoint |
{{access_token}} |
Access token returned from Authentication against the Norwegian IDP |
{{patientPid}} |
The patient identifier (for example DNR, FNR) |
The response to this call will contain the ticket in the following format (example):
8fbc3754-9d3c-41ce-9569-1e3ac301ba3f
or in JSON format if proper accept header is present in the request: (accept : application/json)
{
"patientTicket": "12ff19c0-9598-4c7d-bfb1-224be2c24e2b"
}
The current expiry time for the ticket is given in response header “Expires”. The lifeltime for the ticket is a “sliding window”, as new requests for the same patient will “renew” the same ticket for a new time window.
Having an open portal with an active session active, the patientTicket is used in setPatient postMessage to activate the patient display. (note that before this happens, the portal will show at welcome message that is not intended for the end-user)
// Start patient call
let msg = {
'action': 'setPatient',
'ticket' : result,
'showAllergies': 'true',
'onBehalfOf': ''
};
console.debug('Send setPatient post message...');
iframe = document.getElementById('sfmclientFrame');
iframe.contentWindow.postMessage(msg, '*');
Variable | Meaning |
---|---|
action |
setPatient to set the event of opening the patient in the Patient portal, in other portals this event will not work. |
ticket |
patient ticket from API |
showAllergies |
true or false if you want to show allergies tab in the UI |
onBehalfOf |
Set an HprId if acting on behalf of another practitioner |
After a successful setPatient the portal shows data for the specific patient.
Generating the nonce
In order to login to the SFM you need to generate a nonce to properly validate the identity of the request.
It is important to note that the nonce is used in two different versions: A hashed (and base64 encoded) version as input to session/create, and a plain (and base64 encoded) version in the browser integration (the login message).
When login is peformed by the browser, the code from session response and the plain nonce is received by the server, and by hashing the nonce, SFM is able to compare and verify the connection to the token receivced by session/create
The nonce is a random number generated and converted to Base64 that will then need to be hashed. In the example below this is a 64 byte array.
A detailed walkthroug
This is a walkthroug of the normal sequence for a practitioner to open a pateient window in SFM.
EPJ/EHR creates a nonce
in two variants:
1: Base64 encoded version used in login message to iframe:
qkhV08YwfJFk97VB3XP1t6WaZ2OaTMs8Y98HxGJgoHu9VGTrwN6R9te57JeOidhszqgOMpnCv5O6+X+F/8hWpA==
2: Sha512 AND Base64 encoded version presented i session api for session/Create:
sqGnrWdjgWN2COtLrXMYu9+xER5P9r4+UbepqyQh0gJThofaSXan2djVtnMGuLVHgrx+mOFdeCrtbQykEwR2rw==
EPJ retrieves token for the Practitioner:
EPJ performs session/Create to SFM:
https://session.test2.forskrivning.no/api/Session/create
Content-Type : application/json
Autorization : Bearer ey....
POST Request body { "nonce" : "sqGnrWdjgWN2COtLrXMYu9+xER5P9r4+UbepqyQh0gJThofaSXan2djVtnMGuLVHgrx+mOFdeCrtbQykEwR2rw==" }
Repsonse body { "id": "aJBdvkk5j3wW7utUfqmQDwRMCVIRVsFDxK7jHXZo8JabFahppgKxHo7DoLHJgHLAQH12YG7SamXdCtELicb3UQ==", "code": "haORVSl6uxbB7HjEj/4lByfMNZMEbxibGz/NMwZoSTe7VDAHsvi/04/aNptXulq+OXuHg7SrpjN2h8J8RJ65tg==", "clientAddress": "https://client.test2.forskrivning.no/v30", "apiAddress": "https://server.test2.forskrivning.no/v30", "metadata": { "displayportal": "https://display-portal.test2.forskrivning.no/v30", "patientportal": "https://client.test2.forskrivning.no/v30", "enterpriseportal": "https://enterprise-portal.test2.forskrivning.no/v30", "healthcareportal": "https://health-personnel-portal.test2.forskrivning.no/v30" } }
EPJ opens web-window with SFM “Patientportal” in iframe:
<iframe id="sfm_iframe" src="https://client.test2.forskrivning.no/v30/" width="100%" height="800px">
Parent frame listens to event “message” from the iframe: window.addEventListener("message", dispayMessage, false);
Message received when SFM client is loaded and ready: {"clientLoaded":"success"}
EPJ is now ready to login the Practitioner, using the “plain” nonce and the code from session/Create response.
Using postMessage on the iframe.contentWindow:
jason {"action":"login","code":"haORVSl6uxbB7HjEj/4lByfMNZMEbxibGz/NMwZoSTe7VDAHsvi/04/aNptXulq+OXuHg7SrpjN2h8J8RJ65tg==","nonce":"qkhV08YwfJFk97VB3XP1t6WaZ2OaTMs8Y98HxGJgoHu9VGTrwN6R9te57JeOidhszqgOMpnCv5O6+X+F/8hWpA==","apiEndpoint":"https://server.test2.forskrivning.no/v30"}
EPJ receives:
{"login":"success"}
EPJ is now ready to select patient. Patient ticket is obtained in API by presenting known identifier for the patient:
https://session.test2.forskrivning.no/api/PatientTicket
Content-Type : application/json
Autorization : Bearer ey....
POST Request body { "patientPid" : "10086148248" }
Repsonse: "b0968242-4df2-47cc-b3b9-2040d3886c46"
The response header contains Expires indicating lifetime of the ticket)
Note: By adding Request header: Accept : application/json the response body will appear as JSON : { "patientTicket" : "b0968242-4df2-47cc-b3b9-2040d3886c46" }
EPJ may now select the patient to show in the portal by postMessage to iframe:
{"action":"setPatient","ticket":"b0968242-4df2-47cc-b3b9-2040d3886c46","showAllergies":true,"onBehalfOf":""}
EPJ receives:
{"setPatient":"success"}
EPJ now shows the patient medication in the iframe!
Display-portal (AKA: widget)
The Display portal is slightly different in tree ways:
it is possible to create a session for display portal with a token without the “identity” part. The EPJ must provide information (PID) for the user requiring the portal. See example below.
The display portal needs two boolean values for controlling the appearence of Allergies and medications:
{"action":"setPatient","ticket":"3f682b1c-e54e-4693-929d-20698935d9a5","showAllergies":true,"showLib":true,"onBehalfOf":""}
Excess parameters are ignored, the above format works for all “setPatient” messages.
The portal redirects to outer window
HelseID token with EPJ claim for user identification: