Skip to content

Commit

Permalink
Merge pull request LinuxForHealth#40 from Alvearie/security_option_2
Browse files Browse the repository at this point in the history
Security option 2
  • Loading branch information
mcbride-p authored Mar 7, 2022
2 parents b9b487b + de10219 commit d9974d9
Show file tree
Hide file tree
Showing 8 changed files with 173 additions and 32 deletions.
Binary file added .DS_Store
Binary file not shown.
47 changes: 47 additions & 0 deletions README-Health-Data-Access-Pattern_Integration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# How to Securely Deploy Patient Browser Application by integrating with a Health Data Access Pattern FHIR server

## Introduction

Instructions are provided below for a configuration to securely deploy the Patient Browser Application by integrating with a FHIR server protected by a SMART App Launch authorization server that is built on Keycloak, as per the [Health Data Access Reference Implementation](https://github.com/Alvearie/health-patterns/tree/main/data-access). Note that this is a local deployment end to end - with Patient Browser, IBM FHIR server and Keycloak service each running in separate docker containers.


## Deployment

### Health Data Access Pattern Deployment

Deploy the Health Data Access Pattern as per [here](https://github.com/Alvearie/health-patterns/tree/main/data-access) but with the following additional steps carried out within the Keycloak Admin Console;

1. Select 'Client' from LHS menu.
2. Click on 'inferno' client from Client pane.
3. If you don't want additional Consent screen after login screen the first time you try to log into Patient Browser, then set 'Consent Required' to 'OFF' for 'inferno' client.
4. Add 'http://127.0.0.1:8081/*' to list of 'Valid Redirect URLs'
5. Add 'http://127.0.0.1:8081' to 'Web Origins' list.


### Patient Browser Deployment

Git clone this repository and cd into this directory;

```bash
git clone https://github.com/Alvearie/patient-browser
```

Run the following to refresh the build;

```bash
npm i
```

Modify build/config/default.json5 as follows;
1. Set server.url parameter to ‘https://localhost:9443/fhir-server/api/v4'
2. Set authEnabled to ‘true’

Start Patient Browser running on node http server locally by running;

```bash
npm start
```

URL to access Patient Browser deployment is -> http://127.0.0.1:8081

When prompted for username/password enter fhiruser/change-password
Binary file modified build/.DS_Store
Binary file not shown.
1 change: 1 addition & 0 deletions build/config/default.json5
Original file line number Diff line number Diff line change
Expand Up @@ -2480,6 +2480,7 @@
},
},
patientsPerPage: 25,
authEnabled: false,
timeout: 20000,
renderSelectedOnly: false,
fhirViewer: {
Expand Down
8 changes: 8 additions & 0 deletions build/config/keycloak.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"realm": "test",
"auth-server-url": "https://localhost:8443/auth",
"ssl-required": "external",
"resource": "inferno",
"public-client": true,
"confidential-port": 0
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
"if-env": "^1.0.0",
"jquery": "^3.2.1",
"json5": "^0.5.1",
"keycloak-js": "^17.0.0",
"less": "^2.7.2",
"less-loader": "^4.0.3",
"mixin-deep": "^1.2.0",
Expand Down
109 changes: 89 additions & 20 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,27 +8,96 @@ import PatientList from "./components/PatientList";
import { Router, Route, Switch } from "react-router";
import createHistory from "history/createHashHistory";
import jQuery from "jquery";
import Keycloak from "keycloak-js";
import JSON5 from "json5";
import { parseQueryString } from "./lib";

window.$ = window.jQuery = jQuery;

const history = createHistory();

ReactDOM.render(
<Provider store={STORE}>
<Router history={history}>
<Switch>
<App>
<Route path="/" component={PatientList} exact />
<Route path="/patient/:index" component={PatientDetail} />
</App>
</Switch>
</Router>
</Provider>,
document.getElementById("main")
);

$(function () {
$("body").tooltip({
selector: ".patient-detail-page [title]",
});
let authEnabled = false;
const DEFAULT_CONFIG = "default";
let { config, ...params } = parseQueryString(window.location.search);

jQuery.ajax({
url: `./config/${config || DEFAULT_CONFIG}.json5`,
dataType: "text",
cache: false,
async: false,
success: (json) => {
json = JSON5.parse(json);
authEnabled = json.authEnabled;
},
});

if (authEnabled) {
let keycloak = new Keycloak("config/keycloak.json");

keycloak.onTokenExpired = function () {
keycloak.updateToken().then((refreshed) => {
if (refreshed) {
sessionStorage.setItem("access-token", keycloak.token);
}
});
};

keycloak
.init({
onLoad: "login-required",

scope: "patient/*.read",
})
.then(function (authenticated) {
if (authenticated) {
sessionStorage.setItem("access-token", keycloak.token);

const history = createHistory();

ReactDOM.render(
<Provider store={STORE}>
<Router history={history}>
<Switch>
<App>
<Route path="/" component={PatientList} exact />
<Route path="/patient/:index" component={PatientDetail} />
</App>
</Switch>
</Router>
</Provider>,
document.getElementById("main")
);

$(function () {
$("body").tooltip({
selector: ".patient-detail-page [title]",
});
});
}
})
.catch(function () {
alert(
"Failed to initialize KeyCloak adapter. Check KeyCloak config file."
);
});
} else {
const history = createHistory();

ReactDOM.render(
<Provider store={STORE}>
<Router history={history}>
<Switch>
<App>
<Route path="/" component={PatientList} exact />
<Route path="/patient/:index" component={PatientDetail} />
</App>
</Switch>
</Router>
</Provider>,
document.getElementById("main")
);

$(function () {
$("body").tooltip({
selector: ".patient-detail-page [title]",
});
});
}
39 changes: 27 additions & 12 deletions src/lib/PatientSearch.js
Original file line number Diff line number Diff line change
Expand Up @@ -836,18 +836,33 @@ export default class PatientSearch {
if (server.type == "DSTU-2") {
data = data.replace(/\bdeceased=(true|false)\b/gi, "");
}

// prepare the base options for the patient ajax request
let options = {
url: `${server.url}/Patient/_search`,
method: "POST",
processData: false,
data,
headers: {
accept: "application/fhir+json",
"content-type": "application/x-www-form-urlencoded",
},
};
// Take Access token from session storage if its available ie. if KeyCloak enabled authentication enabled.
// And only include authorization header if access token is available
let keycloakToken = sessionStorage.getItem("access-token");

let options = keycloakToken
? {
url: `${server.url}/Patient/_search`,
method: "POST",
processData: false,
data,
headers: {
accept: "application/fhir+json",
"content-type": "application/x-www-form-urlencoded",

authorization: "Bearer " + keycloakToken,
},
}
: {
url: `${server.url}/Patient/_search`,
method: "POST",
processData: false,
data,
headers: {
accept: "application/fhir+json",
"content-type": "application/x-www-form-urlencoded",
},
};

return this.getPatientIDs(server)
.then((ids) => {
Expand Down

0 comments on commit d9974d9

Please sign in to comment.