1.0.0 • Published 1 year ago

@i4mi/js-on-fhir v1.0.0

Weekly downloads
20
License
Apache-2.0
Repository
github
Last release
1 year ago

@i4mi/js-on-fhir FHIR® library with OAuth 2.0 for web applications built with JavaScript frameworks

Wrapper for the I4MI FHIR® resources, inheritance and type definitions library @i4mi/fhir_r4.

This library handles the OAuth 2.0 authorization process, providing essential functionality for the client side. It also provides other functionality to interact with a given FHIR server. It can be used in web applications that are built with JavaScript frameworks (e.g. Angular, Vue.js and Quasar).

See below for instructions for using it with Vue.js.

If you are updating from an earlier version of this library to 1.0.0 or higher, you may have to adapt some of your code. See the list of breaking changes in chapter 6.

Content

1 Usage

1.1 Install package

Install package with:

npm i @i4mi/js-on-fhir

1.2 Import and initialize module

To use js-on-fhir, you have to import it on the entry page on your project.

import { JSOnFhir } from '@i4mi/js-on-fhir';

1.2.1 Constructor

Then you can declare an instance, passing the needed parameters to the constructor:

const fhir = new JSOnFhir('serverUrl', 'clientId', 'redirectUrl');

where the parameters correspond to:

  • serverUrl: The URL of the FHIR server you want to communicate with.
  • clientId: The ID of your FHIR application as registered with the FHIR server.
  • redirectUrl: The URL the server can talk back to your app during the auth process. When testing locally, this may look like http://localhost:8080 or similar. The page loaded from this exact URL must call the handleAuthResponse() function (see below). Also mind that the redirectUrl may have to be registered server-side for security reasons.
  • options: Optional parameter. Options you want to pass as an object literal to the constructor for configuring the jsOnFhir object.
    • doesNotNeedAuth: Optional parameter. Set to true when the FHIR server you're using doesn't require authentication (e.g. when connecting to the EPD playground via MobileAccessGateway). In this case, the parameters clientId and redirectUrl do not matter (but still have to be set.)
    • disablePkce: Optional parameter. Set to true if you want to use the OAuth 2.0 authorization code flow instead of the recommended and more secure PKCE flow or the server does not support PKCE.
    • fhirVersion: Optional parameter. Specify the FHIR version you want to use (and that is supported by the server). Default value is '4.0.1'.

The constructor keeps track of your jsOnFhir instances and persists them over page reloads, as long as you keep the browser session. This means that when you call the constructor with the same parameters again during a session, the earlier created instance is restored instead of creating a new one, including all the auth details.

1.2.2 Authentication free FHIR servers

If you want to use js-on-fhir with a FHIR server that does not need authentication (e.g. when accessing the EPD Playground with the Mobile Access Gateway, or when you're doing the FHIR Drills tutorial), you can set the optional doesNotNeedAuth parameter in the constructor via the options object literal to true:

const fhir = new JSOnFhir('serverUrl', '', '', { doesNotNeedAuth: true });

With this setting, you will be able to do read and / or write requests (depending on the server's configuration) without having to deal with the auth process. The second and third parameter of the constructor (clientId and redirectUrl) can be empty strings, but should not be null or undefined.

1.2.3 Authorization without PKCE extension

Per default jsOnFhir uses the OAuth 2.0 Authorization Code Grant with the PKCE (RFC 7636): Proof Key for Code Exchange extension. The PKCE is an extension to the authorization code flow to prevent CSRF and authorization code injection attacks. However, if the FHIR server you are using does not support this extension, you can set the optional disablePkce parameter in the constructor via the options object literal to true:

const fhir = new JSOnFhir('serverUrl', 'clientId', 'redirectUrl', { disablePkce: true });

1.3 Auth process

Before you can read or write to the FHIR server, you need to authenticate with OAuth 2.0. As mentioned in section 1.2.3, this plugin supports the OAuth 2.0 Authorization Code Grant with the PKCE extension. Strictly speaking this plugin can't completely handle the whole OAuth 2.0 procedure. It provides necessary client side functionality, but there are still dependencies which arise from the auth server and have to be handled by the latter.

1.3.1 Starting the auth process

For triggering the auth process, you can call the authenticate() method from anywhere in your web application. When everything is configured properly, this opens a page for your user to log in with their credentials. The authorization server then validates the request to ensure that all required parameters are present and valid. If the request is valid, this page redirects to the redirectUrl given before. This is known as the authorization response.

1.3.2 Second step of the auth process

For handling the authorization response, you need to call the handleAuthResponse() method from the page that is loaded when calling the given redirectUrl. You don't have to differentiate if the page was called by the server's auth page or just by using your app, this is handled by the method itself.

What happens behind the scenes is that this plugin makes a token request after recieving the authorization response. The response of the token endpoint consists of the access and the refreshToken which will be saved into the sessionStorage. This method therefore returns a promise that resolves with the server's response when the authentication and token request was successful, or is rejected with the error message if either one wasn't (when the method was called during a non-auth-related page reload, the promise resolves with null).

Unless you want to store the refresh token, you usually don't have to do anything with the server response here, since the plugin handles everything auth related for you.

1.3.4 Refreshing the authentication

The access token received during the auth process is only valid for a given time, usually in a range of hours. After this time, or when having a new browser session, you need to re-authenticate, either by restarting the auth process or by using a refresh token, that is included in the server's auth response.

So if you want to use the refresh token for re-authentication, you have to persist it when you get the server's auth response. Please mind that the refresh token for a health record is considered sensible information and has to be saved securely.

let refreshToken;
fhir.handleAuthResponse()
.then((res) => {
  // check if the response is not null
  if (res) {
    // we are authenticated
    // ... and can keep refreshToken
    refreshToken = res.refresh_token;
  }
})
.catch((err) => {
  // oops, something went wrong
  console.log(err);
});

// later, we can use the refresh token for re-authentication
fhir.refreshAuth(refreshToken)
.then((res) => {
  /* do something*/
})
.catch((err) => {
  /* do something */
});

The refresh token is only valid once, so the refreshAuth() method also returns a promise with the server response, including a new refresh token. You can save this new refresh token the same way as you did when calling handleAuthResponse().

1.4 Methods

The following table describes all the methods intended for public use.

FunctionDescriptionParamsReturns
authenticate(params?)Starts the two-step authentication process (see 1.3.1).params: (optional) additional params to be added to the auth URL, as key/value object.nothing(but redirects to the server's auth page)
handleAuthResponse()Handles the callback by the auth server. Has to be called when loading the page by the redirectUrl (see 1.2.1). The returned auth token is handled by the plugin and does not require further action.noneA promise that resolves to a) the server's response when in the auth process and the request was successful (HTTP status 200 / 201)b) null when not in the auth process or c) rejects with an error message when in the auth process and an error occurred.
refreshAuth(rToken)Refreshes the authentication with a refresh token. The returned auth token is handled by the plugin and does not require further action.rToken: a refresh token that was saved from an earlier server auth response.A promise that resolves to the server's response (including a new refresh token) or rejects with an error message.
isLoggedIn()Checks if an auth token is set and not expired.nonetrue if a token is set and not yet expired, false if no token is set, or it is expired.
logout()Logs out the user by deleting all the authentication information.nonenothing
create(resource)Creates a new resource on the FHIR server.resource: the resource to create.A promise that: resolves with the created resource if successful (HTTP status 200 / 201), or rejects with an error message.
update(resource)Updates an existing resource on the FHIR server.resource: the resource to update. Note that the resource.id must be set and correct.A promise that:resolves with the updated resource if successful (HTTP status 200 / 201), or rejects with an error message.
search(resourceType, params?)Searches for resources on the FHIR server.resourceType: the resource type to look up.params: (optional) the FHIR search parameters (see hl7.org for details).A promise that:a) resolves to the server's response (a FHIR bundle with the search results) if sucessful orb) rejects with an error message.
getResource(resourceType, id)Fetches a resource with known id from the FHIR server.resourceType: the resource type to look up.id: The unique FHIR id of the resource.A promise that:a) resolves to the server's response (as a Resource object, not a Bundle) if sucessful orb) rejects with an error message.
performOperation(operation, payload?, httpMethod?, params?, resourceType?, resourceId?)Performs a FHIR Operation, as for example processing a FHIR Message, on the FHIR server.operation: The operation type to perform, without leading $ sign (use process-message, not $process-message).payload (optional): Input payload for the operationhttpMethod (optional): Specify the HTTP method for the operation (GET, POST, PUT or DELETE, default is POST.)params (optional): Parameter for the operation, either as a key/value pair object (like {id: 'a1b2c3'}) or as a string of url parameters (like ?id=a1b2c3) resourceType (optional): String indicating the resource type to perform the operation on (e.g. Observation).resourceId (optional): Use resource ID (e.g. s) to specify the resource instance to perform the operation on. Can only be used in combination with a resourceType.A promise that:a) resolves to the server's response (a FHIR bundle with the search results) if sucessful orb) rejects with an error message.
getUserId()Gets the resource ID of the current user, if logged in. With this user, the corresponding Patient resource (or Practitioner resource, when logged in as Health Professional or Researcher) can be fetched.noneThe ID of the Patient or Practitioner resource of the currently logged in user. undefined if no user is logged in.
setLanguage(lang)Sets the language used for the server's auth window (if supported server-side).lang: The abbreviation of the wanted language (e.g. 'en', 'de', 'fr' or 'it').nothing
setConformanceUrl(url)Manually sets the conformance URL. Only necessary if it deviates from the standard myserver.net/fhir/metadata scheme, as this is generated as a default.url: the server's conformance URL.nothing
setScope(scope)Manually sets the scope. Only necessary if scope differs from the default user/*.*.scope: the desired scope.nothing
changeFhirVersion(version)Select the FHIR version to use in the requests.version: The FHIR version to use (caution: support of versions can be restricted on the server used). Supported versions are: STU3 (3.0.2), R4 (4.0.1), R4B (4.3.0) and R5 (5.0.0)nothing

1.5 Examples of Interactions with a FHIR server

1.5.1 Search

Searching for resources returns a bundle with all resources matching the search criteria (in the example: all resources of type "Observation" since the Titanic sank on April 15th in 1912.)

fhir.search('Observation', { date: 'ge1912-04-15' })
.then((response) => {
  // response is now the server response with the resource in the body
  // only if status is 200 or 201

  // print the id of the first resource in the response bundle
  console.log(response.entry[0].resource.id);
}).catch((error) => {
  // here you can / have to handle everything that can go wrong
  // (everything other than status 200 / 201)
});

1.5.2 Create

Creates a FHIR resource. IMPORTANT: Do not forget to set the resourceType key!

let myResource = {
  "resourceType": "Observation",
  /* ... */
}
fhir.create(myResource)
.then((response) => {
  // response is now the server response with the resource in the body
  // only if status is 200 or 201
  }).catch((error) => {
  // here you can / have to handle everything that can go wrong
  // (everything other than status 200 / 201)
});

1.5.3 Update

Updates a FHIR resource. IMPORTANT: Do not forget that you need the resource.id for updating!

fhir.update(myResource)
.then((response) => {
  // response is now the server response with the resource in the body
  // only if status is 200 or 201
  }).catch((error) => {
  // here you can / have to handle everything that can go wrong
  // (everything other than status 200 / 201)
});

2 Using with Vue.js

If you want to use jsOnFhir in a Vue.js project, it is recommended to declare the instance in your main.js and then make it available globally.

2.1 Make your jsOnFhir instance globally available

This is achieved by adding a getter function for the jsOnFhir instance to the Vue prototype. Your main.js then should look like this:

2.1.1 Setting up main.js (Vue 2)

import Vue from 'vue'
import App from './App.vue'


/* snip: insert this to your main.js */

// import JSonFhir after installing it from npm
import { JSOnFhir } from '@i4mi/js-on-fhir'
// declare a constant with your correct parameters
const fhir = new JSOnFhir(
  'serverUrl',
  'clientId',
  'redirectUrl',
  {
    disablePkce: false,
    doesNotNeedAuth: false
  }
);
// attach a getter function to the Vue prototype to make it globally available
Object.defineProperties(Vue.prototype, {
  $fhir: {
    get: function() {
      return fhir;
    }
  }
});
/* snap - that's it */

Vue.config.productionTip = false
new Vue({
  render: function (h) { return h(App) },
}).$mount('#app')

2.1.2 Setting up main.js (Vue 3)

In Vue 3, setting global properties works a bit different:

import { createApp } from 'vue';
import App from './App.vue';

const app = createApp(App);

/* snip: insert this to your main.js */

// import JSonFhir after installing it from npm
import { JSOnFhir } from '@i4mi/js-on-fhir';
// set global property
app.config.globalProperties.$fhir = new JSOnFhir(
  'serverUrl',
  'clientId',
  'redirectUrl',
  {
    disablePkce: false,
    doesNotNeedAuth: false
  }
);
/* snap - that's it */

app.mount('#app');

2.1.3 Accessing global jsOnFhir in the components (Vue 2 and 3)

After setting up the globally available jsOnFhir instance (see 2.1.1 or 2.1.2), it can be accessed in every component like this:

let loggedIn = this.$fhir.isLoggedIn();

This works in every of your components, without the need to import the package or initialize a new jsOnFhir instance.

2.2 Handle the two-step auth process

For handling the two-step auth process, you have to include the handleAuthResponse() method into the mounted() section on the root view of your project (or whatever page you pointed the redirectUrl to). In a default Vue.js project, this would be App.vue.

/* ... */
mounted(){ // mounted() is automatically executed every time your Vue component is mounted
  this.$fhir.handleAuthResponse()
  .then(response => {
    if(response){ // check if response is not null - we have to check for this,
      // or we will overwrite the auth token every time when reloading the component

      // when we get to here, we are authenticated
      console.log("logged in?", this.$fhir.isLoggedIn()); // see?

      let refreshToken = response.refresh_token;
      // keep this refreshToken in a safe place
      // e.g. on a post-it attached to your screen ;-)
    }
  })
  .catch(err => {
    // if something went wrong, we end up here
    console.log(err);
  });
}
/* ... */

You then can call the this.$fhir.authenticate() method from anywhere in your project, which takes the user to the server's auth page and further to the defined redirectUrl (which is your App.vue), where the auth response is handled.

3 Demo app

A demonstration of the most important functions and the implementation can be seen in a simple demo app available on i4mi/midata-quasar-starter-app. The demo app is built using the Quasar Framework, which uses Vue.js as an underlying technology and can be used as a starter template for your own project.

4 Dev

If you want to contribute to the plugin, you can clone the repository from GitHub:
git clone https://github.com/i4mi/fhir-wrappers.ts.git

Then check out the development branch (or directly clone it: git clone -b develop https://github.com/i4mi/fhir-wrappers.ts.git).

Then create your own branch with a title, according the feature you want to implement!

install (dev) dependencies npm install --save

build
npm run build

publish: login with i4mi account
npm publish --access public

5 Submit issues

Go to the global repo issue site on GitHub.

Create a new issue with the label .

6 Changelog

6.1 Breaking changes in Version 1.0.0

  • Different methods have now typed return values. In TypeScript projects, this may lead to errors (that are usually easy to fix, though).
  • The search() method now checks the resourceType parameter for validity (if supported by the server, according to the conformance statement). This means that the common practice for passing a whole search string to the method as resourceType does no longer work. Use the params parameter for search params instead.
    • For fetching a resource with a known id, use the new getResource() method instead. The benefit of this is, that the search() method return value can be typed as a Bundle, and the getResource() return value can be typed as a Resource.
  • The getPatient() method is deprecated and has been renamed to getUserId(), which is more descriptive (since it returns only the ID and not the whole resource, and can also return the Practitioner ID when logged in as Health Professional or Researcher).
  • The (undocumented) generateRandomState(length) method is no longer available. If you used it to create a prefilled registering link, you can instead use the authenticate() method and pass the prefilled fields as key/value pair.
  • Version 2.0.0 of @i4mi/fhir_r4 is used, which has incompabilities to previous versions. You may have to update your project to Version 2.0.0 of @i4mi/fhir_r4 when using TypeScript.
  • PKCE Code Challenge is used per default for the auth, which may cause problems if your server does not support PKCE. You can disable this behaviour by setting disablePKCE in the option objects of the constructor to true (see chapter 1.2.3)
  • The way the data is stored in the session storage has changed. No action is needed in your application, but the user will have to log in again when you update your existing application to version 1.0.0.
VersionDateChanges
1.0.02022-12-16- Add ability to use PKCE extension. - Adjusted usage of session storage and added an IIFE for not exposing auth information to the parent application. This is for preventing saving things twice, and also for obscuring auth data (however, this is NOT an encription!) - Adjusted constructor. - Added ability to have multiple jsOnFhir instances run in the same project (e.g. for different servers). - Remove deprecated processMessage() method. - Add changeFhirVersion() method. - Add getUserId() method. - Deprecate getPatient() method. - Fix errors in README and add descriptions regarding PKCE and constructor. - Link to the new demo app.
0.3.02022-11-07- Use @i4mi/fhir_r4 version 2.0.0.
0.2.42022-06-28- Use @i4mi/fhir_r4 version 1.1.1, because using 1.1.0 could cause bugs when using Vue.js with vite.
0.2.32022-06-27- Fix bug for performOperation() not using the auth token.
0.2.22022-06-27(skipped for technical reasons)
0.2.12021-11-10- Add performOperation()- Adjusted README for usage with Vue 3.
0.2.02021-11-03- Add processMessage()- Fix errors in README
0.1.02021-10-18- Add ability to use FHIR servers without authentication. - Update some dependencies.- Add changelog to README.- Fix vulnerabilities in packages
0.0.212021-09-06Updated some dependencies.
0.0.202020-11-20Add getAccessToken() method.
0.0.192020-11-03Logout when server responds with 401 or invalid / expired token.
0.0.182020-07-06Update README and make params in search() optional.
0.0.172020-07-02Enable additional (optional) parameters on authenticate().
0.0.162020-06-12Make fetchConformanceStatement() public.
0.0.152020-06-11Make generateRandomState() function public and let it return the state.
0.0.142020-06-09Bugfix.
0.0.132020-06-03Fix a bug where Webapps with '#' in URL could not authenticate.
0.0.122020-06-03- Update dependencies- Add getPatient() method.
0.0.112019-12-02Update README with License and FHIR trademark remarks.
0.0.92019-11-22Fix broxen setLanguage() method.
0.0.62019-09-20Update dependencies and bugfix.
0.0.52019-09-10Add demo app to and fix README.
0.0.42019-09-09Cleanup and adjust README.
0.0.12019-08-29Initial version.

FHIR® is the registered trademark of HL7 and is used with the permission of HL7. Use of the FHIR trademark does not constitute endorsement of this product by HL7.

1.0.0

1 year ago

1.0.0-beta8

1 year ago

1.0.0-alpha.9

1 year ago

1.0.0-alpha.8

1 year ago

1.0.0-alpha.10

1 year ago

1.0.0-alpha.15

1 year ago

1.0.0-alpha.12

1 year ago

1.0.0-alpha.11

1 year ago

1.0.0-alpha.14

1 year ago

1.0.0-alpha.13

1 year ago

1.0.0-beta.11

1 year ago

1.0.0-beta.12

1 year ago

0.3.0

1 year ago

1.0.0-beta.10

1 year ago

1.0.0-beta.15

1 year ago

1.0.0-beta.13

1 year ago

1.0.0-beta.14

1 year ago

1.0.0-beta.2

1 year ago

1.0.0-beta.3

1 year ago

1.0.0-beta.4

1 year ago

1.0.0-beta.5

1 year ago

1.0.0-beta.1

1 year ago

1.0.0-beta.6

1 year ago

1.0.0-beta.7

1 year ago

1.0.0-beta.9

1 year ago

0.2.3-rc1

2 years ago

0.2.3

2 years ago

0.2.2

2 years ago

0.2.4

2 years ago

0.2.1

2 years ago

0.2.0

2 years ago

0.1.0

2 years ago

0.0.21

3 years ago

0.0.20

3 years ago

0.0.19

3 years ago

0.0.18

4 years ago

0.0.17

4 years ago

0.0.16

4 years ago

0.0.15

4 years ago

0.0.14

4 years ago

0.0.12

4 years ago

0.0.13

4 years ago

0.0.10

4 years ago

0.0.11

4 years ago

0.0.9

4 years ago

0.0.8

4 years ago

0.0.7

4 years ago

0.0.6

5 years ago

0.0.5

5 years ago

0.0.4

5 years ago

0.0.3

5 years ago

0.0.2

5 years ago

0.0.1

5 years ago