fix/migration 3p11 (#5370)

This commit is contained in:
Alireza authored and GitHub committed 2025-08-28 12:57:45 -04:00
1 parent df0593aac9
commit 6a1838bf0d
391 files changed
+2322 -215

No files matched your search

@@ -0,0 +1,4 @@
{
"label": "Deployment",
"position": 3
}
@@ -0,0 +1,97 @@
---
sidebar_position: 6
sidebar_label: Auth
title: Authorization and Authentication
summary: Guide to configuring OpenID-Connect authentication in OHIF Viewer, including setup of authorization flows, token handling, and implementation details for securing access to medical imaging data.
---
# Authorization and Authentication
The OHIF Viewer can be configured to work with authorization servers that support one or more of the OpenID-Connect authorization flows. The Viewer finds it's OpenID-Connect settings on the oidc configuration key. You can set these values in your configuration files. For instance you can take a look at our
`google.js` configuration file.
```js
oidc: [
{
// ~ REQUIRED
authority: 'https://accounts.google.com',
client_id: '723928408739-k9k9r3i44j32rhu69vlnibipmmk9i57p.apps.googleusercontent.com',
redirect_uri: '/callback',
response_type: 'id_token token',
scope: 'email profile openid https://www.googleapis.com/auth/cloudplatformprojects.readonly https://www.googleapis.com/auth/cloud-healthcare', // email profile openid
// ~ OPTIONAL
post_logout_redirect_uri: '/logout-redirect.html',
revoke_uri: 'https://accounts.google.com/o/oauth2/revoke?token=',
automaticSilentRenew: true,
revokeAccessTokenOnSignout: true,
},
],
```
You need to provide the following information:
- authority: The URL of the authorization server.
- client_id: The client id of your application (provided by the authorization server).
- redirect_uri: The callback URL of your application.
- response_type: The response type of the authorization flow (e.g. id_token token, [learn more about different flows](https://darutk.medium.com/diagrams-of-all-the-openid-connect-flows-6968e3990660)).
- scope: The scopes that your application needs to access
- post_logout_redirect_uri: The URL that the user will be redirected to after logout.
- revoke_uri: The URL that the user will be redirected to after logout.
- automaticSilentRenew: If true, the user will be automatically logged in after the token expires.
- revokeAccessTokenOnSignout: If true, the access token will be revoked on logout.
## How it works
The Viewer uses the `userAuthenticationService` to set the OpenID-Connect settings. The `userAuthenticationService` is a singleton service that is responsible for authentication and authorization. It is initialized by the app and you can grab it
from the `servicesManager`
```js
const userAuthenticationService = servicesManager.services.userAuthenticationService;
```
Then the userAuthenticationService will inject the token as Authorization header in the requests that are sent to the server (both metadata
and pixelData).
## Token based authentication in URL
Sometimes (although not recommended), some servers like to send the token
in the query string. In this case, the viewer will automatically grab the token from the query string
and add it to the userAuthenticationService and remove it from the query string (to prevent it from being logged in the console
in future requests).
and example would be
```js
http://localhost:3000/viewer?StudyInstanceUIDs=1.2.3.4.5.6.6.7&token=e123125jsdfahsdf
```
## Implicit Flow vs Authorization Code Flow
The Viewer supports both the Implicit Flow and the Authorization Code Flow. The Implicit Flow is the default currently, as it is easier to set up and use. However, you can opt for better security by using the Authorization Code Flow. To do so, add `useAuthorizationCodeFlow` to the configuration and change the `response_type` from `id_token token` to `code`.
Read more about Implicit Flow vs Authorization Code Flow [here](https://documentation.openiddict.com/guides/choosing-the-right-flow.html#:~:text=The%20implicit%20flow%20is%20similar,when%20using%20response_mode%3Dform_post%20) and [here](https://medium.com/@alysachan830/the-basics-of-oauth-2-0-authorization-code-implicit-flow-state-and-pkce-ed95d3478e1c)
```js
oidc: [
{
authority: 'https://accounts.google.com',
client_id: '723928408739-k9k9r3i44j32rhu69vlnibipmmk9i57p.apps.googleusercontent.com',
redirect_uri: '/callback',
scope: 'email profile openid',
post_logout_redirect_uri: '/logout-redirect.html',
revoke_uri: 'https://accounts.google.com/o/oauth2/revoke?token=',
revokeAccessTokenOnSignout: true,
automaticSilentRenew: true,
// CHANGE THESE *****************************
response_type: 'code',
useAuthorizationCodeFlow: true,
},
],
```
In fact, since browsers are blocking third-party cookies, the Implicit Flow will cease functioning in the future (not specific to OHIF). Read more [here](https://support.okta.com/help/s/article/FAQ-How-Blocking-Third-Party-Cookies-Can-Potentially-Impact-Your-Okta-Environment?language=en_US). It is recommended to use the Authorization Code Flow and begin migrating to it.
:::note
For the Authorization Code Flow, when authenticating against Google, you must add the `client_secret` to the configuration as well. Unfortunately, this seems to occur only with Google.
:::
@@ -0,0 +1,178 @@
---
sidebar_position: 12
title: Microsoft Azure Integration
summary: Comprehensive guide for configuring OHIF with Microsoft Azure Healthcare APIs, including step-by-step instructions for Azure AD registration, DICOM service setup, CORS configuration, and OAuth authentication implementation.
---
# Microsoft Azure
This guide explains how to configure a DICOM datasource in OHIF using Azure Healthcare APIs. It focuses on the configuration details and parameters necessary for integration.
---
## Configuring Azure Healthcare APIs as a DICOMweb Data Source
Follow these steps to set up Azure as a DICOM datasource for the OHIF Viewer.
---
### Azure AD Registration:
1. Navigate to the Azure Portal.
2. Select **"Azure Active Directory"** > **"App registrations"** > **"New registration"**.
3. Name your application.
4. Under **"Supported account types"**, select **"Accounts in any organizational directory (Any Azure AD directory - Multitenant) and personal Microsoft accounts (e.g. Skype, Xbox)"**.
5. Enter the following values in your redirect URI tab:
![Redirect URI](../assets/img/azure4.png)
---
### API Permissions:
1. Under your registered application, go to **"API permissions"**.
2. Click **"Add a permission"**.
3. Choose the Azure API for DICOM (**Dicom.ReadWrite**). If you can't find it, refer to the "Configure Azure DICOMWEB Service" section and then return to this step.
![API Permissions](../assets/img/azure1.png)
---
### Authentication:
1. Under **"Authentication"**, check the **"ID tokens"** box since we are using OpenID Connect.
---
### App Client ID and Tenant ID:
1. Copy your app client ID and tenant ID to prepare for use in configuring an OHIF datasource.
---
### Consent:
1. The first time a user logs in, they will be prompted to consent to the permissions your application has requested.
2. Once they grant consent, your application can use the obtained access token to call the specific Microsoft API on behalf of the user.
![Consent](../assets/img/azure5.png)
---
### Configure Azure DICOMWEB Service:
1. **Create a Health Data Services workspace**:
![Create Workspace](../assets/img/azure6.png)
2. Visit the newly created workspace and press **"Deploy DICOM Service"**:
![Deploy DICOM Service](../assets/img/azure7.png)
3. After the DICOM service is deployed, visit the **"CORS headers"** section:
![CORS Headers](../assets/img/azure8.png)
4. Set the headers and origins to `*` and specify the HTTP methods you'd like to use:
![Set Headers](../assets/img/azure9.png)
5. Save the changes.
6. Add the Microsoft emails of the users you'd like to grant access to your DICOM service in the **"Access control"** section and assign them the **"DICOM Data Owner"** role (or other roles depending on your requirements):
![Access Control](../assets/img/azure10.png)
7. Copy your DICOM service URL to prepare it for usage in OHIF as a datasource:
![DICOM Service URL](../assets/img/azure3.png)
8. Upload your DICOM files to your service.
---
## 1. Configure OIDC Authentication
Azure uses OpenID Connect (OIDC) for authentication. Update the OIDC section in your configuration file with the following parameters:
```json
"oidc": [
{
"redirect_uri": "/callback",
"response_type": "id_token token",
"scope": "openid https://dicom.healthcareapis.azure.com/Dicom.ReadWrite",
"post_logout_redirect_uri": "/logout-redirect.html",
"automaticSilentRenew": false,
"revokeAccessTokenOnSignout": true,
"loadUserInfo": false,
"authority": "https://login.microsoftonline.com/{tenant-id}/v2.0/",
"client_id": "{client-id}"
}
]
```
#### Parameters:
- **redirect_uri**: The URL where users are redirected after successful authentication.
- **response_type**: Specifies the authentication response type (id_token and token).
- **scope**: Defines the level of access. Use `Dicom.ReadWrite` to allow read and write access to DICOM data.
- **post_logout_redirect_uri**: The URL users are redirected to after logout.
- **automaticSilentRenew**: Automatically renews tokens without user interaction. Set to `false` for manual renewal.
- **revokeAccessTokenOnSignout**: Revokes access tokens upon logout for added security.
- **loadUserInfo**: Disables loading additional user information; set to `false` for Azure as it is not supported.
- **authority**: The Azure AD tenant URL for OIDC authorization.
- **client_id**: The application’s client ID from Azure AD.
---
## 2. Add the Data Source Configuration
Update the data source configuration file with your Azure Healthcare APIs details:
```json
{
"namespace": "@ohif/extension-default.dataSourcesModule.dicomweb",
"sourceName": "ohif_azure",
"friendlyName": "ohif_azure",
"configuration": {
"singlepart": "bulkdata,pdf,video",
"imageRendering": "wadors",
"thumbnailRendering": "wadors",
"supportsWildcard": true,
"enableStudyLazyLoad": true,
"supportsFuzzyMatching": false,
"supportsStow": true,
"qidoRoot": "https://{your-dicom-instance}.dicom.azurehealthcareapis.com/v2",
"wadoUriRoot": "https://{your-dicom-instance}.dicom.azurehealthcareapis.com/v2",
"wadoRoot": "https://{your-dicom-instance}.dicom.azurehealthcareapis.com/v2"
}
}
```
#### Parameters:
- **qidoRoot**: Base URL for QIDO-RS queries.
- **wadoUriRoot**: Base URL for WADO-URI requests.
- **wadoRoot**: Base URL for WADO-RS requests.
---
## 3. Running the Viewer with Azure Configuration
1. Save the above configurations in your OHIF Viewer configuration file.
2. Run the viewer:
```bash
cd OHIFViewer
yarn install
APP_CONFIG=config/azure.js yarn run dev
```
Replace `config/azure.js` with the path to your configuration file.
---
### Additional Notes
- Ensure that the Azure Healthcare API is enabled for your subscription and that the necessary permissions (e.g., `Dicom.ReadWrite`) are assigned to the OIDC client.
- The `qidoRoot`, `wadoUriRoot`, and `wadoRoot` should point to your Azure DICOM service URL. Replace `{your-dicom-instance}` with your actual instance name.
This setup allows OHIF to interact seamlessly with Azure's Healthcare APIs, enabling robust DICOM management and visualization.
@@ -0,0 +1,132 @@
---
sidebar_position: 2
title: Build for Production
summary: Step-by-step guide to building a production-ready version of the OHIF Viewer, including environment setup, code acquisition, dependency restoration, production build creation, and configuration options for deployment.
---
# Build for Production
### Build Machine Requirements
- [Node.js & NPM](https://nodejs.org/en/download/)
- [Yarn](https://yarnpkg.com/lang/en/docs/install/)
- [Git](https://www.atlassian.com/git/tutorials/install-git)
### Getting the Code
_With Git:_
```bash
# Clone the remote repository to your local machine
git clone https://github.com/OHIF/Viewers.git
```
More on: _[`git clone`](https://git-scm.com/docs/git-clone),
[`git checkout`](https://git-scm.com/docs/git-checkout)_
_From .zip:_
[OHIF/Viewers: master.zip](https://github.com/OHIF/Viewers/archive/master.zip)
### Restore Dependencies & Build
Open your terminal, and navigate to the directory containing the source files.
Next run these commands:
```bash
# If you haven't already, enable yarn workspaces
yarn config set workspaces-experimental true
# Restore dependencies
yarn install
# Build source code for production
yarn run build
```
If everything worked as expected, you should have a new `dist/` directory in the
`platform/app/dist` folder. It should roughly resemble the following:
```bash title="<root>platform/app/dist/"
├── app-config.js
├── app.bundle.js
├── app.css
├── index.html
├── manifest.json
├── service-worker.js
└── ...
```
By default, the build output will connect to OHIF's publicly accessible PACS. If
this is your first time setting up the OHIF Viewer, it is recommended that you
test with these default settings. After testing, you can find instructions on
how to configure the project for your own imaging archive below.
### Configuration
The configuration for our viewer is in the `<root>platform/app/public/config`
directory. Our build process knows which configuration file to use based on the
`APP_CONFIG` environment variable. By default, its value is
[`config/default.js`][default-config]. The majority of the viewer's features,
and registered extension's features, are configured using this file.
The easiest way to apply your own configuration is to modify the `default.js`
file. For more advanced configuration options, check out our
[configuration essentials guide](../configuration/configurationFiles.md).
## Next Steps
### Deploying Build Output
_Drag-n-drop_
- [Netlify: Drop](./static-assets#netlify-drop)
_Easy_
- [Surge.sh](./static-assets#surgesh)
- [GitHub Pages](./static-assets#github-pages)
_Advanced_
- [AWS S3 + Cloudfront](./static-assets#aws-s3--cloudfront)
- [GCP + Cloudflare](./static-assets#gcp--cloudflare)
- [Azure](./static-assets#azure)
### Testing Build Output Locally
A quick way to test your build output locally is to spin up a small webserver.
You can do this by running the following commands in the `dist/` output
directory:
```bash
# Install http-server as a globally available package
yarn global add http-server
# Change the directory to the platform/app
cd platform/app
# Serve the files in our current directory
npx serve ./dist -c ../public/serve.json
```
### Automating Builds and Deployments
If you found setting up your environment and running all of these steps to be a
bit tedious, then you are in good company. Thankfully, there are a large number
of tools available to assist with automating tasks like building and deploying
web application. For a starting point, check out this repository's own use of:
- [CircleCI][circleci]: [config.yaml][circleci-config]
- [Netlify][netlify]: [netlify.toml][netlify.toml] |
[build-deploy-preview.sh][build-deploy-preview.sh]
<!-- prettier-ignore-start -->
[circleci]: https://circleci.com/gh/OHIF/Viewers
[circleci-config]: https://github.com/OHIF/Viewers/blob/master/.circleci/config.yml
[netlify]: https://app.netlify.com/sites/ohif/deploys
[netlify.toml]: https://github.com/OHIF/Viewers/blob/master/platform/app/netlify.toml
[build-deploy-preview.sh]: https://github.com/OHIF/Viewers/blob/master/.netlify/build-deploy-preview.sh
<!-- prettier-ignore-end -->
@@ -0,0 +1,146 @@
---
sidebar_position: 8
title: Cross-Origin Resource Sharing
summary: Detailed explanation of cross-origin security configurations for OHIF Viewer, covering CORS requirements for data source access, iframe embedding, secure contexts, and troubleshooting techniques for proper implementation.
---
# Cross-Origin Information for OHIF
This document describes various security configurations, settings and environments/contexts needed to fully leverage OHIF’s capabilities. One may need some configurations while others might need ALL of them - it all depends on the environment OHIF is expected to run in.
In particular, three of OHIF’s features depend on these configurations:
- [Embedding OHIF in an iframe](#embedding-ohif-in-an-iframe)
- [XMLHttpRequests to fetch data from data sources](#cors-in-ohif)
## Embedding OHIF in an iframe
As described [here](./iframe.md), there are cases where OHIF will be embedded in an iframe. The following links provide more information for setting up and configuring OHIF to work in an iframe:
- [OHIF iframe documentation](./iframe.md#static-build)
- [OHIF as a Cross-origin Resource in an iframe](#ohif-as-a-cross-origin-resource-in-an-iframe)
## Secure Context
MDN defines a secure context as [“a Window or Worker for which certain minimum standards of authentication and confidentiality are met.“](https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts)
Any local URL is considered secure. The following are some examples of local URLs that are considered secure…
- http://localhost
- http://127.0.0.1:3000
URLs that are NOT local must be delivered over `https://` or `wss://` (i.e. TLS) to be considered secure. See [When is a context considered secure](https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts#when_is_a_context_considered_secure) in MDN for more information.
### iframes
A page embedded in an iframe is considered secure if it itself and every one of its embedding ancestors are delivered securely. Otherwise it is deemed insecure.
### Configuring/setting up a secure context
[Local URLs are considered secure](https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts#when_is_a_context_considered_secure), and as such whenever OHIF is accessed via a local URL (e.g. http://localhost:3000) it is running in a secure context. For example, in a development environment using the default webpack setup, OHIF can be deployed and accessed in a secure context at http://localhost:3000.
The best alternative is to host OHIF over HTTPS.
:::tip
OHIF can be served over HTTPS in a variety of ways (these are just some examples).
- Website hosting services that offer HTTPS deployment (e.g,. Netlify) or offer HTTPS load balancers (AWS, Google Cloud etc.)
- Setting up a reverse proxy (e.g. `nginx`) with a self-signed certificate that forwards requests to the OHIF server
- [An OHIF Docker image can be set up this way](./docker/docker.md#ssl).
:::
## Origin Definition
According to [MDN](https://developer.mozilla.org/en-US/docs/Glossary/Origin), a Web content’s origin is defined by the scheme (protocol), hostname (domain), and port of the URL used to access it. Two objects have the same origin only when the scheme, hostname, and port all match.
## CORS - Cross-Origin Resource Sharing
A cross-origin resource is a resource (e.g. image, JSON, etc) that is served by one origin and used/referenced by a different origin.
CORS is the protocol utilized by web servers and browsers whereby a server of one origin identifies and/or restricts which of its resources that other origins (i.e. other than its own) a browser should allow access to. By default a browser does not permit cross-origin resource sharing.
The CORS mechanism relies on the HTTP response headers from the server to indicate if a resource can be shared with a different origin.
See the [MDN CORS article](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) for more information.
### CORS HTTP Headers
The header that mostly concerns OHIF is listed below and should be configured accordingly on the DICOMweb server or any data source that OHIF would make XMLHttpRequests to for its data.
```http
Access-Control-Allow-Origin: `<origin>` | *
```
:::tip
The `Access-Control-Allow-Origin` header specifies which origins can access the served resource embedded in the response.
Either a single, specific origin (i.e. `<origin>`) can be specified or ALL origins (i.e. *)
See [MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#access-control-allow-origin) for more information.
:::
### CORS in OHIF
OHIF fetches and displays data and images from data sources. It invokes XMLHttpRequests to some data sources such as DICOMweb data sources to fetch the information to render. Typically, a DICOMweb server is hosted on a completely different origin than the one serving OHIF. As such, those XMLHttpRequests use CORS.
### Troubleshooting CORS in OHIF
The following is an example screenshot of the browser console when one of OHIF’s DICOMweb data source servers is not configured for CORS.
![CORS browser console errors](../assets/img/cors-browser-console-errors.png)
And the following is what is in the accompanying network tab.
![CORS browser network panel errors](../assets/img/cors-network-panel-errors.png)
:::info
Setting the appropriate CORS header varies per server or service that is hosting the data source. What follows below is just one example to remedy the problem.
:::
:::tip
If Orthanc is the data source running in a Docker container composed with/behind nginx. And OHIF is being served at localhost:3000. The issue can be remedied by adding either of the following to Orthanc’s Docker container nginx.conf file.
```nginx
add_header 'Access-Control-Allow-Origin' 'http://localhost:3000' always;
```
Or
```nginx
add_header 'Access-Control-Allow-Origin' '*' always;
```
:::
### Header Values (see [MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cross-Origin_Resource_Policy#usage) for more information)
|Value|Description|
|-----|-----------|
|same-site|Only requests from the same site can read the resource.|
|same-origin|Only requests from the same origin can read the resource.|
|cross-origin|Requests from any origin can read the resource. The value is useful and [exists](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cross-Origin_Resource_Policy#relationship_to_cross-origin_embedder_policy_coep) primarily for letting documents with the [COEP require-corp value](#header-values-pertinent-to-ohif-see-mdn-for-more-information-1) know that the resource is ok to be embedded|
### OHIF and CORP
#### PDF from a Cross Origin DICOMweb Data Source
There are some DICOMweb data sources (e.g. dcm4chee) whereby OHIF uses the data source’s `/rendered` endpoint to embed a DICOM PDF document in the OHIF DOM using an `<object>` tag.
As specified for the [COEP require-corp value](#header-values-pertinent-to-ohif-see-mdn-for-more-information-1), a page like OHIF with COEP header `require-corp` can embed cross-origin resources in DOM elements that have the [`crossorigin` attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/crossorigin) OR the resource is delivered with an appropriate CORP header. The `<object>` tag does NOT support the `crossorigin` attribute. As such, the PDF must be delivered with a CORP header.
:::tip
Setting the CORP header varies per server or service that is hosting the data source. The following is just one example.
For a dcm4chee DICOMweb data source composed in Docker behind nginx, the CORP header can be configured in the nginx.conf file as such:
```nginx
add_header 'Cross-Origin-Resource-Policy' 'cross-origin' always;
```
If the dcm4chee server and the OHIF server are hosted on the same site, then the following would also work:
```nginx
add_header 'Cross-Origin-Resource-Policy' 'same-site' always;
```
:::
@@ -0,0 +1,117 @@
---
sidebar_position: 4
title: Custom URL Access/Build
summary: "Instructions for hosting the OHIF Viewer on custom URL paths, with two deployment approaches: simple setup for serving the viewer from a subpath with assets at the root, and advanced setup for custom asset paths with detailed configuration steps."
---
# Hosting the Web Viewer on a Custom URL Path
You can host the viewer on a subpath like `/abc` instead of the root `/`. There are **two levels** of customization depending on how you want to serve your static assets.
## Simple Setup (Recommended for Most Use Cases)
If you want to make the viewer accessible from a custom path (e.g. `/abc`) and **don’t care where the assets are loaded from** (they’ll be fetched from the root `/`), this setup is for you.
### What You Get
- Viewer available at `https://yourdomain.com/abc`
- All assets (JS, WASM, etc.) still loaded from the root (`/app.js`, `/viewer.wasm`, etc.)
### How To Set It Up
1. Set `routerBasename` in your config file (e.g., `config/myConfig.js`) to `/abc`
2. Build the viewer with:
```bash
APP_CONFIG=config/myConfig.js yarn build
```
### Local Development
```bash
APP_CONFIG=config/myConfig.js yarn dev
```
---
## Advanced Setup (Custom Asset Path)
If you want to host the viewer at `/abc` **and** serve static assets from a different location (e.g. `/my-private-assets`), this is a more advanced scenario.
### What You Get
- Viewer accessible from `https://yourdomain.com/abc`
- All assets loaded from `https://yourdomain.com/my-private-assets/`
### Why This is Tricky
Some libraries (especially ones using WASM) load assets using **relative paths**, which can break if not handled carefully. To solve this:
- Set `routerBasename` to `/abc`
- Set `PUBLIC_URL` to `/my-private-assets/`
- Webpack will load assets from the specified public URL.
### Local Development Notes
In development, use proxy rewrites to handle relative asset paths. Example for `dicom-microscopy-viewer`:
```js
proxy: {
'/dicom-microscopy-viewer': {
target: 'http://localhost:3000',
pathRewrite: {
'^/dicom-microscopy-viewer': `/${PUBLIC_URL}/dicom-microscopy-viewer`,
},
},
}
```
This ensures local development can find assets even when libraries expect them at certain paths.
### Building and Serving in Production
To build the viewer for production:
```bash
PUBLIC_URL=/my-private-assets/ APP_CONFIG=config/myConfig.js yarn build
```
If you're using `npx serve`, make sure to update `serve.json`:
```json
{
"rewrites": [{ "source": "*", "destination": "/abc/index.html" }]
}
```
Serve the viewer like this:
```bash
cd platform/app
mv dist abc # Rename dist folder to match your viewer route
npx serve -c ./public/serve.json
```
---
### 🐳 Using Docker? You're Covered
If you’re using our Dockerfile, you’re all set — it already handles copying specific asset folders (like `dicom-microscopy-viewer`) to the root:
```Dockerfile
COPY --from=builder /usr/src/app/platform/app/dist/dicom-microscopy-viewer /usr/share/nginx/html/dicom-microscopy-viewer
```
Keep an eye on the browser network tab for any assets that might fail to load — if any other libraries require similar treatment, you’ll need to handle those as well.
---
## Summary
| Goal | routerBasename | PUBLIC_URL | Assets Load From |
|-----------------------------------|----------------|-----------------------|--------------------------|
| Viewer at `/abc`, assets from `/` | `/abc` | default | Root `/` |
| Viewer at `/abc`, assets from `/my-private-assets` | `/abc` | `/my-private-assets/` | `/my-private-assets/` |
@@ -0,0 +1,212 @@
---
sidebar_position: 4
title: Docker Deployment
summary: Comprehensive guide for deploying OHIF Viewer using Docker, covering pre-built images from Docker Hub, custom image building, configuration options through build arguments and environment variables, and runtime container management.
---
# Docker
The OHIF source code provides a [Dockerfile](https://github.com/OHIF/Viewers/blob/master/Dockerfile) to create and run a Docker image that containerizes an [nginx](https://www.nginx.com/) web server serving the OHIF Viewer.
:::info
This Dockerfile is the same used to generate the [OHIF image(s) on Docker Hub](https://hub.docker.com/r/ohif/app/tags).
:::
## Running the Docker Container with our pre-built images from Docker Hub
To run the Docker container, use the following command based on whether you're targeting a release or beta version. (Learn more about versioning [here](../../development/getting-started.md#branches).)
```sh
# beta version
docker run -d -p 3000:80 ohif/app:v3.10.0-beta.33
# release version
docker run -d -p 3000:80 ohif/app:v3.9.2
```
This will run the Docker container and serve the OHIF Viewer at `http://localhost:3000`. You can name the container anything you want by adding the `--name` flag (e.g., `docker run -d -p 3000:80 --name ohif-viewer-container ohif/app:v3.10.0-beta.33`).
## Building the Docker Image From Source
:::tip
Building a Docker image comes in handy when OHIF has been customized (e.g. with custom extensions, modes, hanging protocols, etc.). For convenience, there are basic OHIF images built in Docker Hub. Find the latest [release](https://hub.docker.com/r/ohif/app/tags?page=1&name=latest) and [dev](https://hub.docker.com/r/ohif/app/tags?page=1&name=beta) images all in Docker Hub.
:::
### Prerequisites
The machine on which to build and run the Docker container must have:
1. All of the [requirements](../build-for-production.md#build-for-production) for building a production version of OHIF.
2. A checked out branch of the OHIF Viewer.
3. [Docker](https://docs.docker.com/get-docker/) installed.
### Building the Docker Image
:::info
In this tutorial, we will build the Docker image for the OHIF Viewer and OHIF server as defined in the `default.js` config which points to our server and our studies.
If you need the Viewer to show your own server studies, you need to build the viewer with a custom configuration that points to your server and your studies.
You can set build arguments to point to your custom configuration file. For more information on data sources, see [here](../../platform/extensions/modules/data-source.md).
:::
To build the Docker image from the terminal:
- Navigate to the OHIF Viewer code root directory (base of the monorepo).
- Run a basic Docker build command:
```sh
docker build . -t ohif-viewer-image
```
*Note*: The name `ohif-viewer-image` is an example. You can replace it with any name and tag of your choice by changing the `-t` value (e.g., `-t my-image:latest`). This naming is arbitrary for local Docker images.
- To customize the build, you can include optional build arguments to set defaults for the app configuration, public path, or port:
```sh
docker build . -t ohif-viewer-image \
--build-arg APP_CONFIG=config/e2e.js \
--build-arg PUBLIC_URL=/ohif/ \
--build-arg PORT=6000
```
#### Available Build Arguments (Optional)
You can use the following build arguments to customize the Docker image:
- `APP_CONFIG`: (Optional) Sets the default app configuration (e.g., `config/e2e.js`). This value can be overridden later by setting an environment variable (you can set it in the docker run command).
- `PUBLIC_URL`: (Optional) Specifies the public path for serving the OHIF Viewer (e.g., `/ohif/`). This value is baked into the build and cannot be changed without rebuilding the image.
- `PORT`: (Optional) Sets the application’s port.
#### Examples of Using Build Arguments
Here are examples of how to use the `--build-arg` option:
- Set the public path:
```sh
docker build . --build-arg PUBLIC_URL=/ohif/
```
- Set a custom app configuration:
```sh
docker build . --build-arg APP_CONFIG=config/kheops.js
```
- Specify a port:
```sh
docker build . --build-arg PORT=6000
```
- Combine multiple arguments:
```sh
docker build . --build-arg PUBLIC_URL=/ohif/ --build-arg APP_CONFIG=config/kheops.js --build-arg PORT=6000
```
:::info PUBLIC_URL Explanation
The `PUBLIC_URL` build argument sets the public path for serving the OHIF Viewer. For example, using `--build-arg PUBLIC_URL=/ohif/` will serve the worklist at `http://host/ohif/` and the viewer at `http://host/ohif/viewer`. While the worklist is also accessible at `http://host/`, it redirects to the `PUBLIC_URL`.
:::
---
## Running the Docker Container
After building the Docker image, you can run it as a container using the following command. The name of the Docker image (`ohif-viewer-image`) is specified at the end, while the flags control various runtime settings.
```sh
docker run -d -p 3000:80/tcp --name ohif-viewer-container ohif-viewer-image
```
- `-d`: Runs the container in the background and prints the container ID.
- `-p {host-port}:{nginx-port}/tcp`: Maps the container's `nginx` port to a port on the host machine. For example, `3000:80` maps host port 3000 to container port 80.
- `--name`: Assigns an arbitrary name to the container for easy identification (e.g., `ohif-viewer-container`).
### Configuring the `nginx` Listen Port
The `nginx` server uses the `{PORT}` environment variable to determine the listening port inside the container. By default, this is set to `80`. You can override it during runtime or build:
#### Setting the Port at Runtime
Use the `-e PORT={container-port}` flag to set the listening port and publish it with `-p`. For example, the following command sets the container port to `8080` and maps it to host port `3000`:
```sh
docker run -d -e PORT=8080 -p 3000:8080/tcp --name ohif-viewer-container ohif-viewer-image
```
#### Setting the Port During Build
To bake the port configuration into the Docker image, use the `--build-arg PORT={container-port}` flag when building the image:
```sh
docker build . --build-arg PORT=8080
```
then you can run the container with the following command:
```sh
docker run -d -p 3000:8080/tcp --name ohif-viewer-container ohif-viewer-image
```
---
### Specifying the OHIF Configuration File
You can specify the OHIF configuration file for the container in three ways:
1. **[Build Default](#build-default)**: Set the default configuration file during the build process.
2. **[Volume Mounting](#volume-mounting)**: Mount a local configuration file into the container.
3. **[Environment Variable](#environment-variable)**: Pass the configuration file contents directly as an environment variable.
#### Build Default
Set the configuration file during the build process using the `--build-arg APP_CONFIG={config-path}` flag. For example:
```sh
docker build . --build-arg APP_CONFIG=config/kheops
```
---
#### Volume Mounting
To use a local configuration file, mount it as a volume during runtime. For example, to use a file located at `/path/to/config/file.js`, use the `-v` flag:
```sh
docker run -d -p 3000:80/tcp -v /path/to/config/file.js:/usr/share/nginx/html/app-config.js --name ohif-viewer-container ohif-viewer-image
```
:::tip
Ensure the path to the local configuration file is absolute, as some Docker versions require it.
:::
---
#### Environment Variable
Alternatively, you can specify the configuration file contents directly as an environment variable (`APP_CONFIG`). This method is useful in environments like Google Cloud.
**Important**: The `APP_CONFIG` variable must contain the file's contents, not its file path. Use the `cat` command to read the file and pass its contents as the environment variable:
```sh
docker run -d -p 3000:80/tcp -e APP_CONFIG="$(cat /path/to/the/config/file)" --name ohif-viewer-container ohif-viewer-image
```
:::tip
- Remove single-line comments (`//`) from the configuration file to prevent issues when serving the file to the OHIF client.
- As an alternative to the `cat` command, you can convert the file to a single line and copy-paste it directly. Tools like [Visual Studio Code](https://stackoverflow.com/questions/46491061/shortcut-for-joining-two-lines) and [Notepad++](https://superuser.com/questions/518229/how-do-i-remove-linebreaks-in-notepad) offer "Join Lines" commands to help with this.
- If both the [Volume Mounting](#volume-mounting) and [Environment Variable](#environment-variable) methods are used, the Volume Mounting method takes precedence.
:::
---
This rewrite improves readability by reorganizing information into smaller, clear sections and providing consistent formatting for examples and tips.
@@ -0,0 +1,128 @@
---
sidebar_position: 2
title: SSL Configuration for Docker
summary: Guide to configuring SSL for OHIF Viewer in Docker deployments, including environment variable setup, certificate mounting, permissions management, and instructions for both CA-signed and self-signed certificate implementation.
---
# SSL
:::caution
We make no claims or guarantees regarding this section concerning security. If in doubt, enlist the help of an expert and conduct proper audits.
:::
If OHIF is not deployed over SSL, this means information transferred to/from OHIF is not encrypted. Consideration must be given as to whether OHIF should be deployed in a secure context over SSL.
### Specifying the SSL Port, Certificate and Private Key
For convenience, the [built Docker image](#building-the-docker-image) can be run over SSL by
- setting the `{SSL_PORT}` environment variable
- volume mounting the SSL certificate
- volume mounting the SSL private key
:::info
The volume mounted SSL certificate and private key are mapped to the [`ssl_certificate`](http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_certificate) and [`ssl_certificate_key`](http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_certificate_key) `nginx` directives respectively.
:::
Similar to the [`nginx` listen port](#configuring-the-nginx-listen-port), the `{SSL_PORT}` environment variable is the internal port that `nginx` listens on to serve the OHIF web server over SSL and has to be likewise published via the `-p` switch.
The following is an example command running the Docker container over SSL. Note that depending on the version of Docker, an absolute path to the certificate and private key files might be required.
```sh
docker run -d -e SSL_PORT=443 -p 3003:443/tcp -v /path/to/certificate:/etc/ssl/certs/ssl-certificate.crt -v /path/to/private/key:/etc/ssl/private/ssl-private-key.key --name ohif-viewer-container ohif-viewer-image
```
:::caution
The above deploys OHIF over SSL using `nginx`'s default SSL configuration. For further OHIF server hardening and security configuration, consider enlisting an expert and then editing OHIF's `nginx` [SSL template configuration file](https://github.com/OHIF/Viewers/blob/8a8ae237d26faf123abeb073cbf0cd426c3e9ef2/.docker/Viewer-v3.x/default.ssl.conf.template) with further [security settings](https://nginx.org/en/docs/http/ngx_http_ssl_module.html) and [tweaks](http://nginx.org/en/docs/http/configuring_https_servers.html) and then [build a new Docker image](#building-the-docker-image) from there.
:::
:::caution
The private key is a secure entity and should have restricted access. Keep it safe!
:::
:::caution
The presence of the `{SSL_PORT}` environment variable is used to trigger to deploy over SSL as opposed to HTTP. If `{SSL_PORT}` is NOT defined, then HTTP is used even if the certificate and private key volumes are mounted.
:::
:::tip
The read and write permissions of the source, mounted volumes are preserved in the Docker container. The volume mounted certificate and private key require read permission.
One way to ensure both are readable is to issue the following on the host system terminal prior to running the Docker container and mounting the certificate and private key volumes.
```sh
sudo chmod 644 /path/to/certificate /path/to/private/key
```
:::
:::tip
The SSL certificate and private key can be either [CA issued](#ca-signed-certificates) or [self-signed](#self-signed-certificates).
:::
### CA Signed Certificates
According to [SSL.com](https://www.ssl.com/faqs/what-is-a-certificate-authority/), a global certificate authority (CA) is a trusted authority and organization that guarantees the identity of other, third-party entities and guarantees the integrity of the electronic information (e.g. web site data) those third-party entities provide and deliver.
There are many globally trusted CAs. Below is a non-exhaustive list of some CAs including links to some documentation for creating and installing certificates and keys from those authorities to be used with `nginx`.
- [GoDaddy](https://ca.godaddy.com/help/nginx-install-a-certificate-6722)
- [Let's Encrypt](https://www.nginx.com/blog/using-free-ssltls-certificates-from-lets-encrypt-with-nginx/)
- [digicert](https://www.digicert.com/kb/csr-ssl-installation/nginx-openssl.htm)
### Self-Signed Certificates
According to [Entrust](https://www.entrust.com/resources/faq/what-is-a-self-signed-certificate), a self-signed certificate is one that is NOT signed by a trusted, public [CA authority](#ca-signed-certificates), but instead (typically) signed by the developer or individual or organization responsible for a web site.
Browsers will treat self-signed certificates as not secure because the signer is not publicly recognized and trusted. When visiting a site encrypted with a self-signed certificate, the browser will present a screen similar to the following warning about the potential risk.
![Self-signed certificate warning](../../assets/img/self-signed-cert-warning.png)
For a self-signed certificate this is normal and expected. Clicking the `Advanced` button displays further information as well as a link for proceeding to site that the certificate is encrypting.
![Self-signed certificate warning](../../assets/img/self-signed-cert-advanced-warning.png)
Self-signed certificates might be appropriate for testing or perhaps deploying a site within an organization's internal LAN. In any case, consult an expert prior to deploying OHIF over SSL.
:::tip
A self-signed certificate can be generated using [`openssl`](https://www.openssl.org/) on the command line.
:::
To create a self-signed certificate:
1. Open a command prompt.
2. Issue the following command:
```sh
sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /desired/key/directory/self-signed-private.key -out /desired/cert/directory/self-signed.crt
```
The chart below describes each of the items in the command.
|Command Item|Description|
|------------|-----------|
|sudo|temporarily grant access as the root/super user to run the `openssl` command|
|openssl|the command line tool for creating and managing certificates and keys|
|req|this together with the subsequent `-x509` indicates to request to generate a self-signed certificate|
|-x509|this together with the `req` indicates to request to generate a self-signed certificate|
|-nodes|skip the option to secure the certificate with a passphrase; this allows `nginx` to start up with without intervention to enter a passphrase each time|
|-days 365|the number of days the certificate will be valid for|
|-newkey rsa:2048|create the a new certificate and key together and make an RSA key that is 2048 bits long|
|-keyout|the path and file name where the private key will be written to|
|-out|the path and file name where the certificate will be written to|
3. Answer the prompts that follow. The table below lists the various prompts. The default value for each prompt is shown within the square brackets. The most important prompt is `Common Name (e.g. server FQDN or YOUR name)`. For this enter the IP address of the OHIF server being secured.
|Prompt|
|------|
|Country Name (2 letter code) [AU]|
|State or Province Name (full name) [Some-State]|
|Locality Name (eg, city) []|
|Organization Name (eg, company) [Internet Widgets Pty Ltd]|
|Organizational Unit Name (eg, section) []|
|Common Name (e.g. server FQDN or YOUR name) []|
|Email Address []|
4. Once completed, the self-signed certificate and private key will be in the locations specified by the `-keyout` and `-out` flags and can be [volume mounted](#specifying-the-ssl-port-certificate-and-private-key) accordingly to the OHIF Docker container.
:::tip
Windows' users can access `openssl` using [Windows Subsystem for Linux (WSL)](https://learn.microsoft.com/en-us/windows/wsl/).
:::
@@ -0,0 +1,141 @@
---
sidebar_position: 9
title: Google Cloud Healthcare Integration
summary: Guide to setting up Google Cloud Healthcare API as a DICOM data source for OHIF, including project creation, API configuration, OAuth authentication setup, and implementation details for connecting the viewer to Google-hosted medical imaging data.
---
# Google Cloud Healthcare
> The [Google Cloud Healthcare API](https://cloud.google.com/healthcare/) is a
> powerful option for storing medical imaging data in the cloud.
An alternative to deploying your own PACS is to use a software-as-a-service
provider such as Google Cloud. The Cloud Healthcare API promises to be a
scalable, secure, cost effective image storage solution for those willing to
store their data in the cloud. It offers an
[almost-entirely complete DICOMWeb API](https://cloud.google.com/healthcare/docs/dicom)
which requires tokens generated via the
[OAuth 2.0 Sign In flow](https://developers.google.com/identity/protocols/oauth2).
Images can even be transcoded on the fly if this is desired.
## Setup a Google Cloud Healthcare Project
1. Create a Google Cloud account
2. Create a project in Google Cloud
A project in Google Cloud can be created by clicking the projects drop down box.
![Google projects drop down](../assets/img/google-projects-drop-down.png)
And then clicking the `NEW PROJECT` button in the top-right corner of the
dialogue that is displayed.
3. Enable the [Cloud Healthcare API](https://cloud.google.com/healthcare/) for your project
:::tip
An API can be enabled through the `APIs & Services > Enabled APIs & Services`
console and clicking the `+ ENABLE APIS AND SERVICES` button.
![Google enable apis](../assets/img/google-enable-apis.png)
:::
:::tip
The principal (i.e. account) that is enabling the Cloud Healthcare API will require
the following roles that can be set in the `IAM & Admin > IAM` console for the
desired project.
- Service Usage Viewer
- Service Usage Admin
:::
:::tip
Roles can be added to a principal in the `IAM & Admin > IAM` console by clicking
the `Edit principal` (i.e. pencil) icon to the right of a principal or by clicking the
`GRANT ACCESS` button at the top of the list of principals. The `GRANT ACCESS`
button is particularly useful if the `Edit principal` icon is disabled.
:::
4. (Optional): Create a Dataset and DICOM Data Store for storing your DICOM data
:::tip
To both list existing datasets as well as create a new dataset for your project,
the principal (i.e. account) must have the following roles enabled
in the `IAM & Admin > IAM` console.
- Editor
:::
5. Enable the [Cloud Resource Manager API](https://cloud.google.com/resource-manager/) for your project.
_Note:_ If you are having trouble finding the APIs, use the search box at
the top of the Cloud console.
6. Go to APIs & Services > OAuth Consent Screen to create an OAuth Consent screen and fill in your application details.
- Run through the three step process of adding an OAuth Consent Screen, clicking `SAVE AND CONTINUE` at the end of each step.
![Google OAuth Consent Screen steps](../assets/img/google-oauth-consent-steps.png)
- For the Scopes step, for Google APIs, click the `ADD OR REMOVE SCOPES` button.
- In the `Update selected scopes` dialogue that flies in from the right, add the
following scopes to the `Manually add scopes` text box.
- `https://www.googleapis.com/auth/cloudplatformprojects.readonly`
- `https://www.googleapis.com/auth/cloud-healthcare`
![Google Manually Add Scopes](../assets/img/google-manually-add-scopes.png)
- Click `ADD TO TABLE` and then click `UPDATE`
7. Go to APIs & Services > Credentials to create a new set of credentials:
- Click `+ CREATE CREDENTIALS` and from the drop down select `OAuth Client ID`.
See [OAuth 2.0 Client ID](https://developers.google.com/identity/protocols/oauth2/) for more information.
![Google Create Credentials](../assets/img/google-create-credentials.png)
- Choose the "Web Application" type
- Add your domain (e.g. `http://localhost:3000`) to the Authorized JavaScript
origins.
- Add your domain, plus `callback` (e.g. `http://localhost:3000/callback`) to the Authorized Redirect URIs.
- Save your Client ID for later.
8. (Optional): Create a bucket containing DICOM files and import it into a Data Store
- When importing a bucket into a Data Store, the following warning might be
displayed indicating that the Cloud Healthcare Service Agent service account associated with the
project does not have the `Storage Object Viewer` role.
![Google Create Credentials](../assets/img/google-healthcare-service-agent-warning.png)
- The Cloud Healthcare Service Agent service account can be displayed in the
`IAM & Admin > IAM` console by checking the `Include Google-provided role grants` checkbox.
The `Storage Object Viewer` role can then be granted to the Cloud Healthcare Service Agent service account.
![Google Provided Accounts Checkbox](../assets/img/google-provided-accounts-checkbox.png)
- More information regarding the Cloud Healthcare Service Agent service account can
be found at https://cloud.google.com/healthcare-api/docs/permissions-healthcare-api-gcp-products
9. (Optional): Enable Public Datasets that are being hosted by Google:
https://cloud.google.com/healthcare/docs/resources/public-datasets/
## Run the viewer with your OAuth Client ID
1. Open the `config/google.js` file and change `YOURCLIENTID` to your Client ID
value.
1. Run the OHIF Viewer using the config/google.js configuration file
```bash
cd OHIFViewer
yarn install
APP_CONFIG=config/google.js yarn run dev
```
## Configuring Google Cloud Healthcare as a datasource in OHIF
A Google Cloud Healthcare DICOM store can be configured as a DICOMweb datasource
in OHIF. A full or partial path is permitted in the configuration file. For
partial paths, the [data source configuration UI](../configuration/dataSources/configuration-ui.md)
will assist in filling in the missing pieces. For example, a configuration with
empty `wadoUriRoot`, `qidoRoot` and `wadoRoot` will prompt for the entire path
step-by-step starting with the project.
@@ -0,0 +1,57 @@
---
sidebar_position: 7
sidebar_label: iframe
title: Embedding OHIF in an iframe
summary: Guidelines for embedding OHIF Viewer within other applications using iframe integration, explaining configuration requirements for path settings, static builds, and proper setup to ensure WebWorkers, WASM, and WebGL features function correctly.
---
# iframe
With the transition to more advanced visualization, loading, and rendering techniques using WebWorkers, WASM, and WebGL, the script tag usage of the OHIF viewer v3 has been deprecated.
An alternative option for script tag usage is to employ an iframe. You can utilize the iframe element to load the OHIF viewer and establish communication with it using the postMessage API if needed.
We recommend utilizing modern development practices and incorporating OHIF viewer within your application using a more modular and integrated approach, such as leveraging bundlers, other UI
components, and frameworks.
## Static Build
You can use the iframe element to load the OHIF viewer as a child element of your application if you need the
viewer to be embedded within your application. The iframe element can be used as follows (use your own custom styles)
```html
<iframe src="./path-to-ohif-build" style="width: 100%; height: 500px; border: none"/>
```
The important thing to note here is that the iframe element is loading the OHIF viewer from the `./path-to-ohif-build`. This path can be
named anything you want, but it should be the path to the OHIF viewer build directory. The build directory is the directory that
contains the `index.html` file (See [build for production](./build-for-production.md) for more information).
It is also required that the PUBLIC_URL environment variable is set to the same path. For example, if the iframe is
`<iframe src="./ohif" />` (which means there is a `ohif` folder containing the build in your main app), then you need to:
1. use a config (e.g. config/myConfig.js) file that is using the `routerBasename` of `/ohif` (note the one / - it is not /ohif/).
2. build the viewer with `PUBLIC_URL=/ohif/ APP_CONFIG=config/myConfig.js yarn build` (note the two / - it is not /ohif).
:::tip
Check to make sure the `app-config.js` in the build is reflecting the correct routerBasename.
:::
:::tip
The PUBLIC_URL tells the application where to find the static assets and the routerBasename will tell the application how to handle the rouets
:::
### Try it locally
Download the index.html and the build (against the /ohif/ path) from [here](https://ohif-assets-new.s3.us-east-1.amazonaws.com/iframe-basic/Archive.zip)
Then run the
```bash
npx http-server unzipped-folder
# you can use npx serve ./dist -c ../public/serve.json as an alternative to http-server
```
You should be able to see
![Alt text](../assets/img/iframe-basic.png)
@@ -0,0 +1,334 @@
---
sidebar_position: 1
sidebar_label: Overview
title: Deployment Overview
summary: Comprehensive guide to deploying the OHIF Viewer as a standalone web application or embedded iframe, covering data source configuration, security considerations, and various deployment options with examples for different hosting environments.
---
# Deployment
The OHIF Viewer can be served as a stand-alone PWA ([progressive web
application][pwa-url]) by building and hosting a collection of static assets or be embedded in other web applications via an iframe if needed. In
either case, you will need to configure your instance of the Viewer so that it
can connect to your data source (the database or PACS that provides the data
your Viewer will display).
:::tip
Our goal is to make deployment as simple and painless as possible; however,
there is an inherent amount of complexity in configuring and deploying web
applications. If you find yourself a little lost, please don't hesitate to
reach out to for help.
:::
## Deployment Scenarios
### Stand-alone Viewer
Deploying the OHIF Viewer as a stand-alone web application provides many
benefits, but comes at the cost of time and complexity. Some benefits include:
_Today:_
- Leverage [extensions](../platform/extensions/index.md) and
[modes](../platform/modes/index.md) to drop-in powerful new features
- Add routes and customize the viewer's workflow
- Finer control over styling and whitelabeling
_In the future:_
- The ability to package the viewer for [App Store distribution][app-store]
#### Hosted Static Assets
At the end of the day, a production OHIF Viewer instance is a collection of
HTML, CSS, JS, Font Files, and Images. We "build" those files from our
`source code` with configuration specific to our project. We then make those
files publicly accessible by hosting them on a Web Server.
##### Part 1 - Build Production Assets
"Building", or creating, the files you will need is the same regardless of the
web host you choose. You can find detailed instructions on how to configure and
build the OHIF Viewer in our
["Build for Production" guide](./build-for-production.md).
##### Part 2 - Host Your App
There are a lot of [benefits to hosting static assets][host-static-assets] over
dynamic content. You can find instructions on how to host your build's output
via one of these guides:
_Drag-n-drop_
- [Netlify: Drop](./static-assets.md#netlify-drop)
_Easy_
- [Surge.sh](./static-assets.md#surgesh)
- [GitHub Pages](./static-assets.md#github-pages)
_Advanced_
- [AWS S3 + Cloudfront](./static-assets.md#aws-s3--cloudfront)
- [GCP + Cloudflare](./static-assets.md#gcp--cloudflare)
- [Azure](./static-assets.md#azure)
### Embedded Viewer (iframe)
`OHIF-v3` has deprecated deploying the viewer as an embedded viewer via a script
tag as the number of underlying libraries that run web workers are increasing for OHIF. An example of these libraries is OHIF's 3D rendering functionality that is provided by
`vtk-js`.
However, you can still embed the viewer using an iframe. You can utilize the iframe element to load the OHIF viewer and establish communication with it using the postMessage API if needed.
Read more about how to use the iframe [here](./iframe.md).
## Data
The OHIF Viewer is able to connect to any data source that implements the [DICOM
Web Standard][dicom-web-standard]. [DICOM Web][dicom-web] refers to RESTful
DICOM Services -- a recently standardized set of guidelines for exchanging
medical images and imaging metadata over the internet. Not all archives fully
support it yet, but it is gaining wider adoption.
### Configure Connection
If you have an existing archive and intend to host the OHIF Viewer at the same
domain name as your archive, then connecting the two is as simple as following
the steps laid out in our
[Configuration Essentials Guide](./../configuration/configurationFiles.md).
#### What if I don't have an imaging archive?
We provide some guidance on configuring a local image archive in our
[Data Source Essentials](./../configuration/dataSources/introduction.md)
guide. Hosting an archive remotely is a little trickier. You can check out some
of our [advanced recipes](#recipes) for modeled setups that may work for you.
#### What if I intend to host the OHIF Viewer at a different domain?
There are two important steps to making sure this setup works:
1. Your Image Archive needs to be exposed, in some way, to the open web. This
can be directly, or through a `reverse proxy`, but the Viewer needs _some
way_ to request its data.
2. \* Your Image Archive needs to have appropriate CORS (Cross-Origin Resource
Sharing) Headers
> \* Cross-Origin Resource Sharing (CORS) is a mechanism that uses additional
> HTTP headers to tell a browser to let a web application running at one origin
> (domain) have permission to access selected resources from a server at a
> different origin. - [MDN Web Docs: Web - Http - CORS][cors]
Most image archives do not provide either of these features "out of the box".
It's common to use IIS, Nginx, or Apache to route incoming requests and append
appropriate headers. You can find an example of this setup in our
[Nginx + Image Archive Deployment Recipe](./nginx--image-archive.md).
#### What if my archive doesn't support DicomWeb?
It's possible to supply all Study data via JSON format, in the event you do not
have a DicomWeb endpoint. You can host all of the relevant files on any web
accessible server (Amazon S3, Azure Blob Storage, Local file server etc.)
This JSON is supplied via the '?url=' query parameter. It should reference an
endpoint that returns **application/json** formatted text.
If you do not have an API, you can simply return a text file containing the JSON
from any web server.
You tell the OHIF viewer to use JSON by using the `dicomjson` datasource and
appending `'?url='` query to your mode's route:
e.g.
`https://my-test-ohif-server/myMode/dicomjson?url=https://my-json-server/study-uid.json`
The returned JSON object must contain a single root object with a 'studies'
array.
You can read more about using different data sources for mode's routes
[here](../platform/modes/routes.md#route-path)
_Sample JSON format:_
```json
{
"studies": [
{
"StudyInstanceUID": "1.2.840.113619.2.5.1762583153.215519.978957063.78",
"StudyDescription": "BRAIN SELLA",
"StudyDate": "20010108",
"StudyTime": "120022",
"PatientName": "MISTER^MR",
"PatientId": "832040",
"series": [
{
"SeriesDescription": "SAG T-1",
"SeriesInstanceUID": "1.2.840.113619.2.5.1762583153.215519.978957063.121",
"SeriesNumber": 2,
"SeriesDate": "20010108",
"SeriesTime": "120318",
"Modality": "MR",
"instances": [
{
"metadata": {
"Columns": 512,
"Rows": 512,
"InstanceNumber": 3,
"AcquisitionNumber": 0,
"PhotometricInterpretation": "MONOCHROME2",
"BitsAllocated": 16,
"BitsStored": 16,
"PixelRepresentation": 1,
"SamplesPerPixel": 1,
"PixelSpacing": [0.390625, 0.390625],
"HighBit": 15,
"ImageOrientationPatient": [0, 1, 0, 0, 0, -1],
"ImagePositionPatient": [11.6, -92.5, 98.099998],
"FrameOfReferenceUID": "1.2.840.113619.2.5.1762583153.223134.978956938.470",
"ImageType": ["ORIGINAL", "PRIMARY", "OTHER"],
"Modality": "MR",
"SOPInstanceUID": "1.2.840.113619.2.5.1762583153.215519.978957063.124",
"SeriesInstanceUID": "1.2.840.113619.2.5.1762583153.215519.978957063.121",
"StudyInstanceUID": "1.2.840.113619.2.5.1762583153.215519.978957063.78"
},
"url": "dicomweb://s3.amazonaws.com/lury/MRStudy/1.2.840.113619.2.5.1762583153.215519.978957063.124.dcm"
}
]
}
]
}
]
}
```
More info on this JSON format can be found here
[Issue #1500](https://github.com/OHIF/Viewers/issues/1500)
**Implementation Notes:**
<!-- 1. When hosting the viewer, you will also need to host a /viewer route on the server - or the browser may not be able to find the route. -->
1. For each instance url (dicom object) in the returned JSON, you must prefix
the `url` with `dicomjson:` in order for the cornerstone image loader to
retrieve it correctly. eg. `https://image-server/my-image.dcm` --->
`dicomjson:https://image-server/my-image.dcm`
2. The JSON format above is compatible with >= v3.7.8 of the application in `V2`
version. Older versions of the viewer used a different JSON format. As of
20/04/20 the public [https://viewer.ohif.org/] is a pre 3.0 version that does
not support this format yet.
3. The JSON format is case-sensitive. Please ensure you have matched casing with
the naturalised Dicom format referenced in
[Issue #1500](https://github.com/OHIF/Viewers/issues/1500).
_CORS Issues (Cross-Origin Resource Sharing)_
If you host a JSON API or Images on a different domain from the app itself,
you will likely have CORS issues. This will also happen when testing from
Localhost and reaching out to remote servers. Even if the domain is the same,
different ports, subdomains or protocols (https vs http) will also cause CORS
errors. You will to need add a configuration on each server hosting these assets
to allow your App server origin.
For example:
Let's assume your application is hosted on `https://my-ohif-server.com`.
Your JSON API is hosted on `https://my-json-api.aws.com`
And your images are stored on Amazon S3 at `https://my-s3-bucket.aws.com`
When you first start your application, browsing to
`https://my-ohif-server.com/myMode/dicomjson?url=https://my-json-api.aws.com/api/my-json-study-info.json`,
you will likely get a CORS error in the browser console as it tries to connect
to `https://my-json-api.aws.com`.
Adding a setting on the JSON server to allow the CORS origin =
`https://my-ohif-server.com` should solve this.
Next, you will likely get a similar CORS error, as the browser tries to go to
`https://my-s3-bucket.aws.com`. You will need to go to the S3 bucket
configuration, and add a CORS setting to allow origin =
`https://my-ohif-server.com`.
Essentially, whenever the application connects to a remote resource, you will
need to add the applications url to the allowed CORS Origins on that resource.
Adding an origin similar to https://localhost:3000 will also allow for local
testing.
### Securing Your Data
Coming soon
<!--
> Feeling lost? Securing your data is important, and it can be hard to tell if
> you've gotten it right. Don't hesitate to work with professional auditors, or
> [enlist help from experts](../help).
The OHIF Viewer can be configured to work with authorization servers that
support one or more of the OpenID-Connect authorization flows. The Viewer finds
it's OpenID-Connect settings on the `oidc` configuration key. You can set these
values following the instructions laid out in the
[Configuration Essentials Guide](./../configuration/index.md).
_Example OpenID-Connect Settings:_
```js
window.config = {
...
oidc: [
{
// ~ REQUIRED
// Authorization Server URL
authority: 'http://127.0.0.1/auth/realms/ohif',
client_id: 'ohif-viewer',
redirect_uri: 'http://127.0.0.1/callback', // `OHIFStandaloneViewer.js`
response_type: 'code', // "Authorization Code Flow"
scope: 'openid', // email profile openid
// ~ OPTIONAL
post_logout_redirect_uri: '/logout-redirect.html',
},
],
}
```
You can find an example of this setup in our
[User Account Control Deployment Recipe](./user-account-control.md).
#### Choosing a Flow for the Viewer
In general, we recommend using the "Authorization Code Flow" ( [see
`response_type=code` here][code-flows]); however, the "Implicit Flow" ( [see
`response_type=token` here][code-flows]) can work if additional precautions are
taken. If the flow you've chosen produces a JWT Token, it's validity can be used
to secure access to your Image Archive as well.
-->
### Recipes
We've included a few recipes for common deployment scenarios. There are many,
many possible configurations, so please don't feel limited to these setups.
Please feel free to suggest or contribute your own recipes.
- [Build for Production](./build-for-production.md)
- [Static](./static-assets.md)
- [Nginx + Image Archive](./nginx--image-archive.md)
- [User Account Control](./user-account-control.md)
<!--
Links
-->
<!-- prettier-ignore-start -->
[viewer-npm]: https://www.npmjs.com/package/@ohif/app
[pwa-url]: https://developers.google.com/web/progressive-web-apps/
[static-assets-url]: https://www.maxcdn.com/one/visual-glossary/static-content/
[app-store]: https://medium.freecodecamp.org/i-built-a-pwa-and-published-it-in-3-app-stores-heres-what-i-learned-7cb3f56daf9b
[dicom-web-standard]: https://www.dicomstandard.org/dicomweb/
[dicom-web]: https://en.wikipedia.org/wiki/DICOMweb
[host-static-assets]: https://www.netlify.com/blog/2016/05/18/9-reasons-your-site-should-be-static/
[cors]: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
[code-flows]: https://medium.com/@darutk/diagrams-of-all-the-openid-connect-flows-6968e3990660
[code-sandbox]: https://codesandbox.io/s/viewer-script-tag-tprch
<!-- prettier-ignore-end -->
@@ -0,0 +1,221 @@
---
sidebar_position: 10
title: Nginx + Image Archive Setup
summary: Tutorial for setting up OHIF Viewer with Nginx and PACS (Orthanc or DCM4CHEE), using Docker for a production-ready system with reverse proxy configuration to securely handle medical imaging data, including installation steps and troubleshooting tips.
---
# Nginx + Image Archive
At a certain point, you may want others to have access to your instance of the
OHIF Viewer and its medical imaging data. This post covers one of many potential
setups that accomplish that. Please note, noticeably absent is user account
control.
Do not use this recipe to host sensitive medical data on the open web. Depending
on your company's policies, this may be an appropriate setup on an internal
network when protected with a server's basic authentication.
### Handling Web Requests
We mitigate our first issue by allowing [Nginx][nginx] to handle incoming web
requests. Nginx is open source software for web serving, reverse proxying,
caching, and more. It's designed for maximum performance and stability --
allowing us to more reliably serve content.
More specifically, we accomplish this by using a
[`reverse proxy`](https://en.wikipedia.org/wiki/Reverse_proxy) to retrieve
resources from our image archive (Orthanc), and when accessing its web admin.
> A reverse proxy is a type of proxy server that retrieves resources on behalf
> of a client from one or more servers. These resources are then returned to the
> client, appearing as if they originated from the proxy server itself.
This setup allows us to create a setup similar to the one pictured below:
![nginX](../assets/img/nginx-image-archive.png)
- All web requests are routed through `nginx` image
- `/pacs/dicom-web` is a reverse proxy for `orthanc`'s `DICOM Web` endpoints, which handles DICOM requests
- `/pacs` is a reverse proxy for `orthanc`'s Web Admin, which is the UI for managing studies
- All static resources for OHIF Viewer are served up by `nginx` when a matching
route for that resource is requested
## Getting Started
### Requirements
- Docker
- [Docker for Mac](https://docs.docker.com/docker-for-mac/)
- [Docker for Windows](https://docs.docker.com/docker-for-windows/)
_Not sure if you have `docker` installed already? Try running `docker --version`
in command prompt or terminal_
### Setup
- `cd platform/app/.recipes/Nginx-Orthanc`
- run: `docker-compose up --build`
- Navigate to `127.0.0.1` for the viewer (at first there is no study)
- Navigate to `127.0.0.1/pacs` for uploading studies via the UI, or send studies via DIMSE C-STORE to `ORTHANC@127.0.0.1:4242` (hint: you can use utilizes like dcm4che's `storescu` to send studies in bulk via the command line)
:::note
For subsequent runs, use `docker-compose up -d` to start the services without rebuilding the images. However, ensure you rebuild the images if you make changes to the Dockerfile. If you modify the configurations in the `nginx.conf` or `orthanc.json` files, you can restart the services by running `docker-compose up`, as these files are mounted as volumes.
```
Inside docker compose file you see the following volumes mounted:
volumes:
# Nginx config
- ./config/nginx.conf:/etc/nginx/nginx.conf
# Logs
- ./logs/nginx:/var/logs/nginx
```
:::
You can see the overview of the mentioned steps:
:::info
The following video demonstrates an outdated capture using the deprecated `OpenResty-Orthanc` recipe. However, it still provides insight into the steps for running the viewer with Orthanc. Use the new `Nginx-Orthanc` recipe for the most up-to-date instructions.
:::
<div style={{padding:"56.25% 0 0 0", position:"relative"}}>
<iframe src="https://player.vimeo.com/video/843233827?badge=0&amp;autopause=0&amp;player_id=0&amp;app_id=58479" frameBorder="0" allow="autoplay; fullscreen; picture-in-picture" allowFullScreen style= {{ position:"absolute",top:0,left:0,width:"100%",height:"100%"}} title="measurement-report"></iframe>
</div>
### Troubleshooting
_Exit code 137_
This means Docker ran out of memory. Open Docker Desktop, go to the `advanced`
tab, and increase the amount of Memory available.
_Cannot create container for service X_
Use this one with caution: `docker system prune`
_X is already running_
Stop running all containers:
- Win: `docker ps -a -q | ForEach { docker stop $_ }`
- Linux: `docker stop $(docker ps -a -q)`
_Traceback (most recent call last):_
_File "urllib3/connectionpool.py", line 670, in urlopen_
_...._
Are you sure your docker is running? see explanation [here](https://github.com/docker/compose/issues/7896)
### Configuration
After verifying that everything runs with default configuration values, you will
likely want to update:
- The domain: `http://127.0.0.1`
#### OHIF Viewer
The OHIF Viewer's configuration is imported from a static `.js` file. The
configuration we use is set to a specific file when we build the viewer, and
determined by the env variable: `APP_CONFIG`. You can see where we set its value
in the `dockerfile` for this solution:
`ENV APP_CONFIG=config/docker-nginx-orthanc.js`
You can find the configuration we're using here:
`/public/config/docker-nginx-orthanc.js`
To rebuild the `webapp` image created by our `dockerfile` after updating the
Viewer's configuration, you can run:
- `docker-compose build` OR
- `docker-compose up --build`
#### Other
All other files are found in: `/docker/Nginx-Orthanc/`
| Service | Configuration | Docs |
| ----------------- | --------------------------------- | ------------------------------------------- |
| OHIF Viewer | [dockerfile][dockerfile] | You're reading them now! |
| Nginx | [`/nginx.conf`][config-nginx] | |
| Orthanc | [`/orthanc.json`][config-orthanc] | [Here][orthanc-docs] |
## Next Steps
### OHIF + Dcm4chee
You can follow the similar steps above to run OHIF Viewer with Dcm4chee PACS.
The recipe for this setup can be found at `platform/app/.recipes/Nginx-Dcm4chee`.
The routes are as follows:
- `127.0.0.1` for the OHIF viewer
- `127.0.0.1/pacs` for the Dcm4chee UI
:::info
For uploading studies, you can see the following gif for the steps:
![alt text](../assets/img/dcm4chee-upload.gif)
:::
### Deploying to Production
While you can deploy this solution to production, there is one main caveat: every user can access the app and the patient portal without any authentication. In the next step, we will add authentication with Keycloak to secure the app.
### Improving This Guide
Here are some improvements this guide would benefit from, and that we would be
more than happy to accept Pull Requests for:
- Add Docker caching for faster builds
### Referenced Articles
For more documentation on the software we've chosen to use, you may find the
following resources helpful:
- [Orthanc for Docker](http://book.orthanc-server.com/users/docker.html)
For a different take on this setup, check out the repositories our community
members put together:
- [mjstealey/ohif-orthanc-dimse-docker](https://github.com/mjstealey/ohif-orthanc-dimse-docker)
- [trypag/ohif-orthanc-postgres-docker](https://github.com/trypag/ohif-orthanc-postgres-docker)
<!--
Links
-->
<!-- prettier-ignore-start -->
<!-- DOCS -->
[nginx]: https://www.nginx.com/resources/glossary/nginx/
[understanding-cors]: https://medium.com/@baphemot/understanding-cors-18ad6b478e2b
[orthanc-docs]: http://book.orthanc-server.com/users/configuration.html#configuration
[lua-resty-openidc-docs]: https://github.com/zmartzone/lua-resty-openidc
<!-- SRC -->
[dockerfile]: https://github.com/OHIF/Viewers/blob/master/platform/app/.recipes/OpenResty-Orthanc/dockerfile
[config-nginx]: https://github.com/OHIF/Viewers/blob/master/platform/app/.recipes/OpenResty-Orthanc/config/nginx.conf
[config-orthanc]: https://github.com/OHIF/Viewers/blob/master/platform/app/.recipes/OpenResty-Orthanc/config/orthanc.json
<!-- prettier-ignore-end -->
@@ -0,0 +1,178 @@
---
sidebar_position: 3
title: Deploy Static Assets
summary: Guide to deploying OHIF Viewer static assets using various hosting options, from simple drag-and-drop methods like Netlify to more advanced cloud platforms like AWS, GCP, and Azure, with step-by-step instructions for each deployment approach.
---
# Deploy Static Assets
> WARNING! All of these solutions stand-up a publicly accessible web viewer. Do
> not hook your hosted viewer up to a sensitive source of data without
> implementing authentication.
There are a lot of options for deploying static assets. Some services, like
`netlify` and `surge.sh`, specialize in static websites. You'll notice that
deploying with them requires much less time and effort, but comes at the cost of
less product offerings.
While not required, it can simplify things to host your Web Viewer alongside
your image archive. Services with more robust product offerings, like
`Google Cloud`, `Microsoft's Azure`, and `Amazon Web Services (AWS)`, are able
to accommodate this setup.
_Drag-n-drop_
- [Netlify: Drop](#netlify-drop)
_Easy_
- [Surge.sh](#surgesh)
- [GitHub Pages](#github-pages)
_Advanced_
- [Deploy Static Assets](#deploy-static-assets)
- [Drag-n-drop](#drag-n-drop)
- [Netlify Drop](#netlify-drop)
- [Easy](#easy)
- [Surge.sh](#surgesh)
- [GitHub Pages](#github-pages)
- [Advanced](#advanced)
- [AWS S3 + Cloudfront](#aws-s3--cloudfront)
- [GCP + Cloudflare](#gcp--cloudflare)
- [Azure](#azure)
## Drag-n-drop
### Netlify Drop
<div style={{padding:"56.25% 0 0 0", position:"relative"}}>
<iframe src="https://player.vimeo.com/video/843233793?badge=0&amp;autopause=0&amp;player_id=0&amp;app_id=58479" frameBorder="0" allow="autoplay; fullscreen; picture-in-picture" allowFullScreen style= {{ position:"absolute",top:0,left:0,width:"100%",height:"100%"}} title="measurement-report"></iframe>
</div>
_GIF demonstrating deployment with Netlify Drop_
1. https://app.netlify.com/drop
2. Drag your `build/` folder on to the drop target
3. ...
4. _annnd you're done_
**Features:**
- Custom domains & HTTPS
- Instant Git integration
- Continuous deployment
- Deploy previews
- Access to add-ons
(Non-free tiers include identity, FaaS, Forms, etc.)
Learn more about [Netlify on their website](https://www.netlify.com/)
## Easy
### Surge.sh
> Static web publishing for Front-End Developers. Simple, single-command web
> publishing. Publish HTML, CSS, and JS for free, without leaving the command
> line.
![surge.sh deploy example](../assets/img/surge-deploy.gif)
_GIF demonstrating deployment with surge_
```shell
# Add surge command
yarn global add surge
# In the build directory
surge
```
**Features:**
- Free custom domain support
- Free SSL for surge.sh subdomains
- pushState support for single page apps
- Custom 404.html pages
- Barrier-free deployment through the CLI
- Easy integration into your Grunt toolchain
- Cross-origin resource support
- And more…
Learn more about [surge.sh on their website](https://surge.sh/)
### GitHub Pages
> WARNING! While great for project sites and light use, it is not advised to use
> GitHub Pages for production workloads. Please consider using a different
> service for mission critical applications.
> Websites for you and your projects. Hosted directly from your GitHub
> repository. Just edit, push, and your changes are live.
This deployment strategy makes more sense if you intend to maintain your project in
a GitHub repository. It allows you to specify a `branch` or `folder` as the
target for a GitHub Page's website. As you push code changes, the hosted content
updates to reflect those changes.
1. Head over to GitHub.com and create a new repository, or go to an existing
one. Click on the Settings tab.
2. Scroll down to the GitHub Pages section. Choose the `branch` or `folder` you
would like as the "root" of your website.
3. Fire up a browser and go to `http://username.github.io/repository`
Configuring Your Site:
- [Setting up a custom domain](https://help.github.com/en/articles/using-a-custom-domain-with-github-pages)
- [Setting up SSL](https://help.github.com/en/articles/securing-your-github-pages-site-with-https)
Learn more about [GitHub Pages on its website](https://pages.github.com/)
## Advanced
All of these options, while using providers with more service offerings,
demonstrate how to host the viewer with their respective file storage and CDN
offerings. While you can serve your static assets this way, if you're going
through the trouble of using AWS/GCP/Azure, it's more likely you're doing so to
avoid using a proxy or to simplify authentication.
If that is the case, check out some of our more advanced `docker` deployments
that target these providers from the left-hand sidepanel.
These guides can be a bit longer and an update more frequently. To provide
accurate documentation, we will link to each provider's own recommended steps:
### AWS S3 + Cloudfront
- [Host a Static Website](https://docs.aws.amazon.com/AmazonS3/latest/dev/website-hosting-custom-domain-walkthrough.html)
- [Speed Up Your Website with Cloudfront](https://docs.aws.amazon.com/AmazonS3/latest/dev/website-hosting-cloudfront-walkthrough.html)
### GCP + Cloudflare
- [Things to Know Before Getting Started](https://code.luasoftware.com/tutorials/google-cloud-storage/things-to-know-before-hosting-static-website-on-google-cloud-storage/)
- [Hosting a Static Website on GCP](https://cloud.google.com/storage/docs/hosting-static-website)
### Azure
- Deploying viewer to Azure blob storage as a static website:
Refer to [Host a static website](https://docs.microsoft.com/en-us/azure/storage/blobs/storage-blob-static-website)
High level steps :
1. Go to Azure portal and create a storage account.
2. Under Overview->Capabilities, select Static website.
3. Enable Static website. Set the index document as ‘index.html’.
4. Copy the primary endpoint. This will serve as the root URL for the viewer.
5. Save. A new container named ‘$web’ will be created.
6. Copy OHIF viewer’s build output from ‘platform\app\dist’ folder to the ‘$web’ container.
7. Open browser and navigate to the viewer root URL copied in the step above. It should display OHIF viewer with data from default data source.
![image](https://github.com/OHIF/Viewers/assets/132684122/236a574b-0f05-4d90-a721-df8720d05949)
Special consideration while accessing DicomJson data source :
• Due to the way routing is handled in react, it may error out in production when trying to display data through dicomJson data source. E.g. https://[Static Website endpoint]/viewer/dicomjson?url= https://ohif-dicom-json-example.s3.amazonaws.com/LIDC-IDRI-0001.json
• Resolution to this is to set error page to ‘index.html’ at the website level. This will ensure that all errors are redirected to root and requests are further served from root path.
![image](https://github.com/OHIF/Viewers/assets/132684122/87696c90-c344-489a-af15-b992434555f9)
- [Add SSL Support](https://docs.microsoft.com/en-us/azure/storage/blobs/storage-https-custom-domain-cdn)
- [Configure a Custom Domain](https://docs.microsoft.com/en-us/azure/storage/blobs/storage-custom-domain-name)
@@ -0,0 +1,526 @@
---
sidebar_position: 11
title: User Account Control
summary: Comprehensive guide for implementing user authentication in OHIF using Keycloak, covering setup with both Orthanc and DCM4CHEE, configuration with OAuth2 proxy, SSL implementation, and detailed steps for local and production deployment scenarios.
---
# User Account Control
:::danger
DISCLAIMER: We make no claims or guarantees regarding the security of this approach. If you have any doubts, please consult an expert and conduct thorough audits.
:::
Making a viewer and its medical imaging data accessible on the open web can
provide a lot of benefits, but requires additional security to make sure
sensitive information can only be viewed by authorized individuals. Most image
archives are equipped with basic security measures, but they are not
robust/secure enough for the open web.
This guide covers one of many potential production setups that secure our
sensitive data.
## Overview
This guide builds on top of our
[Nginx + Image Archive guide](./nginx--image-archive.md),
wherein we used a [`reverse proxy`](https://en.wikipedia.org/wiki/Reverse_proxy)
to retrieve resources from our image archive (Orthanc).
To add support for "User Account Control" we introduce
[Keycloak](https://www.keycloak.org/about.html). Keycloak is an open source
Identity and Access Management solution that makes it easy to secure
applications and services with little to no code. We improve upon our
`reverse proxy` setup by integrating Keycloak and Nginx to create an
`authenticating reverse proxy`.
> An authenticating reverse proxy is a reverse proxy that only retrieves the
> resources on behalf of a client if the client has been authenticated. If a
> client is not authenticated they can be redirected to a login page.
This setup allows us to create a setup similar to the one pictured below:
![userControlFlow](../assets/img/ohif-pacs-keycloak.png)
**Nginx:**
- Acts as a reverse proxy server that handles incoming requests to the domain (mydomain.com:80) and forwards them to the appropriate backend services.
- It also ensures that all requests go through the OAuth2 Proxy for authentication.
**OAuth2 Proxy:**
- Serves as an intermediary that authenticates users via OAuth2.
- Works in conjunction with Keycloak to manage user sessions and authentication tokens.
- Once the user is authenticated, it allows access to specific routes (/ohif-viewer, /pacs, /pacs-admin).
**Keycloak:**
- An open-source identity and access management solution.
- Manages user identities, including authentication and authorization.
- Communicates with the OAuth2 Proxy to validate user credentials and provide tokens for authenticated sessions.
**OHIF Viewer:**
- Hosted under the route /ohif-viewer, which serves the static assets of the OHIF Viewer.
**Orthanc/DCM4chee:**
- PACS (Picture Archiving and Communication System) for managing medical imaging data.
Exposes two routes:
- /pacs: Accesses the DICOM web services.
- /pacs-admin: Provides administrative and explorer interfaces.
## Getting Started - Orthanc
### Requirements
- Docker
- [Docker for Mac](https://docs.docker.com/docker-for-mac/)
- [Docker for Windows](https://docs.docker.com/docker-for-windows/)
_Not sure if you have `docker` installed already? Try running `docker --version`
in command prompt or terminal_
### Setup 1 - Trying Locally
Navigate to the Orthanc Keycloak configuration directory:
`cd platform\app\.recipes\Nginx-Orthanc-Keycloak`
Due to the increased complexity of this setup, we've introduced a magic word `YOUR_DOMAIN`. Replace this word with your project IP address to follow along more easily.
Since we are running this locally, we will use `127.0.0.1` as our IP address.
In the `docker-compose.yml` file, replace `YOUR_DOMAIN` with `127.0.0.1`.
In the Keycloak service:
Before:
```
KC_HOSTNAME_ADMIN_URL: http://YOUR_DOMAIN/keycloak/
KC_HOSTNAME_URL: http://YOUR_DOMAIN/keycloak/
```
After
```
KC_HOSTNAME_ADMIN_URL: http://127.0.0.1/keycloak/
KC_HOSTNAME_URL: http://127.0.0.1/keycloak/
```
In the Keycloak healthcheck, replace `YOUR_DOMAIN` with `localhost`.
In the Nginx config, change:
```
server_name YOUR_DOMAIN;
```
to:
```
server_name 127.0.0.1;
```
Since we're not using SSL, remove the following lines from the Nginx config file and create one server instead of two:
Before (two servers one for http and one for https):
```
server {
listen 80;
server_name YOUR_DOMAIN;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl;
server_name YOUR_DOMAIN;
ssl_certificate /etc/letsencrypt/live/ohifviewer.duckdns.org/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/ohifviewer.duckdns.org/privkey.pem;
root /var/www/html;
```
After (merging both servers into one only http server):
```
server {
listen 80;
server_name 127.0.0.1;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
root /var/www/html;
```
In OAuth2-proxy configuration at `oauth2-proxy.cfg`
Before:
```
redirect_url="http://YOUR_DOMAIN/oauth2/callback"
oidc_issuer_url="http://YOUR_DOMAIN/keycloak/realms/ohif"
```
After:
```
redirect_url="http://127.0.0.1/oauth2/callback"
oidc_issuer_url="http://127.0.0.1/keycloak/realms/ohif"
```
Finally, in the docker-nginx-orthanc-keycloak config file that lives in `platform/app/public/config/docker-nginx-orthanc-keycloak.js`, replace `YOUR_DOMAIN` with
Before:
```
wadoUriRoot: 'http://YOUR_DOMAIN/pacs',
qidoRoot: 'http://YOUR_DOMAIN/pacs',
wadoRoot: 'http://YOUR_DOMAIN/pacs',
```
After:
```
wadoUriRoot: 'http://127.0.0.1/pacs',
qidoRoot: 'http://127.0.0.1/pacs',
wadoRoot: 'http://127.0.0.1/pacs',
```
:::note
This is the config that is used inside the dockerfile to build the viewer, look at dockerfile
`ENV APP_CONFIG=config/docker-nginx-orthanc-keycloak.js`
:::
Run the following command to start the services:
```
docker-compose up --build
```
You can watch the following video, which will guide you through the process of setting up Orthanc with keycloak and OHIF locally.
We have set up two predefined users in Keycloak:
- `user: admin password: admin` - Has access to keycloak portal for managing users and clients
- `user: viewer password: viewer` - Has access to the OHIF Viewer but not the pacs-admin
- `user: pacsadmin password: pacsadmin` - Has access to both the pacs-admin for uploading and the OHIF Viewer
You can navigate to:
- `http://127.0.0.1` - This will redirect you to `http://127.0.0.1/ohif-viewer`, prompting you to log in with Keycloak using either user
- `http://127.0.0.1/pacs-admin` - Only the `pacsadmin` user can access this route, while the `viewer` user cannot
-
<div style={{padding:"56.25% 0 0 0", position:"relative"}}>
<iframe src="https://player.vimeo.com/video/981362196?badge=0&amp;autopause=0&amp;player_id=0&amp;app_id=58479" frameBorder="0" allow="autoplay; fullscreen; picture-in-picture" allowFullScreen style= {{ position:"absolute",top:0,left:0,width:"100%",height:"100%"}} title="measurement-report"></iframe>
</div>
### Step 2 - Trying via a Server
Now that you have successfully set up Orthanc with Keycloak and OHIF locally, you can deploy it to a server. While you can rent a server from any provider, this tutorial will demonstrate the process using Linode as an example.
You can watch the following video, which will guide you through the process.
Some notes:
- Since this is a remote machine we need to clone the repo
- Typically a Linux machine, you need to download and install Docker on it
- Use the Visual Studio Code Remote SSH extension to connect to the server
- Use docker extension in Visual Studio Code to manage the containers
- The public IP address of the server now becomes the YOUR_DOMAIN and is used in the configuration files.
Still we have not set up SSL, so we will use HTTP instead of HTTPS.
We should use the same one server configuration as we did locally for Nginx (but with the new server IP address)
:::info
Don't forget to change the `docker-ngix-orthanc-keycloak.js` file to use the new server IP address.
:::
After you run `docker compose up --build` you can navigate to the server IP address and see the viewer will not work...
We have encountered some strange issues with the Keycloak service not allowing non-HTTPS connections (around 10:00). To resolve this, we need to modify the Keycloak configuration to permit HTTPS. This requires accessing the container and making the necessary changes.
After accessing the container shell
```
cd /opt/keycloak/bin
./kcadm.sh config credentials --server http://localhost:8080 --realm master --user admin
./kcadm.sh update realms/master -s sslRequired=NONE
```
After we need to change some configurations in the Keycloak UI to enable the connection in the server
Navigate to
```
http://IP_ADDRESS/keycloak
```
which will redirect you to the Keycloak login page
0. login with the admin user `admin` and password `admin`
1. From the top left drop down menu, select `ohif` realm
2. Go to `Clients` and select `ohif_viewer`
3. In the `Access Settings` change all instances of `http://127.0.0.1` to `http://IP_ADDRESS`
1. Root URL: `http://IP_ADDRESS`
2. Home URL: `http://IP_ADDRESS`
3. Valid Redirect URIs: `http://IP_ADDRESS/oauth2/callback`
4. Valid post logout URIs: `*`
5. Web Origins: `http://IP_ADDRESS`
6. Admin URL: `http://IP_ADDRESS`
Now if you navigate to the IP address it should work !!
<div style={{padding:"56.25% 0 0 0", position:"relative"}}>
<iframe src="https://player.vimeo.com/video/981362334?badge=0&amp;autopause=0&amp;player_id=0&amp;app_id=58479" frameBorder="0" allow="autoplay; fullscreen; picture-in-picture" allowFullScreen style= {{ position:"absolute",top:0,left:0,width:"100%",height:"100%"}} title="measurement-report"></iframe>
</div>
### Step 3 - Adding SSL and Deploying to Production
Now we'll add an SSL certificate to our server to enable HTTPS. We'll use Let's Encrypt to generate the SSL certificate.
Let's Encrypt requires a domain name, so we'll use a free domain name service like DuckDNS (duckdns.org). Follow these steps:
1. Visit https://www.duckdns.org/ and create an account.
2. Create a free domain name and point it to your server's IP address.
You can watch a video guide for this process if needed.
Replace `YOUR_DOMAIN` with your new domain name in the `docker-compose.yml` file and all other config files, as we did previously.
Next, we'll add HTTPS support. Add the following lines to the Nginx config file:
(Note: We'll have both HTTP and HTTPS servers, and the server IP will use HTTPS)
```
server {
listen 80;
server_name https://IP_ADDRESS;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl;
server_name https://IP_ADDRESS;
ssl_certificate /etc/letsencrypt/live/ohifviewer.duckdns.org/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/ohifviewer.duckdns.org/privkey.pem;
root /var/www/html;
```
Don't forget to replace `YOUR_DOMAIN` with the new domain name in the `docker-nginx-orthanc-keycloak.js` file.
:::info
Remember to include `https://` when adding the domain name to the configurations.
:::
Now, we need to add a certificate. Let's assume we have the domain name `hospital.duckdns.org` and the email we registered with DuckDNS is `your_email@example.com`.
```
docker run -it --rm --name certbot \
-v ./config/letsencrypt:/etc/letsencrypt \
-v ./config/certbot:/var/www/certbot \
-p 80:80 \
certbot/certbot certonly \
--standalone \
--preferred-challenges http \
--email your_email@example.com \
--agree-tos \
--no-eff-email \
-d hospital.duckdns.org
```
:::note
Replace "hospital.duckdns.org" with your domain name and update the email address accordingly.
:::
:::warning
DuckDNS is suitable for testing and demonstration purposes only. For production environments, use a proper domain name and SSL certificate to ensure security.
:::
If you follow these steps, you'll encounter the error `invalid parameter: redirect_uri` when attempting to log in to Keycloak. This occurs because the redirect URL isn't set up correctly in the Keycloak client configuration. To resolve this, we need to log in and adjust these settings.
Navigate to:
```
http://IP_ADDRESS/keycloak
```
Log in using the admin credentials:
- Username: `admin`
- Password: `admin`
Replace all IP addresses with the new domain name, using HTTPS.
<div style={{padding:"56.25% 0 0 0", position:"relative"}}>
<iframe src="https://player.vimeo.com/video/981362676?badge=0&amp;autopause=0&amp;player_id=0&amp;app_id=58479" frameBorder="0" allow="autoplay; fullscreen; picture-in-picture" allowFullScreen style= {{ position:"absolute",top:0,left:0,width:"100%",height:"100%"}} title="measurement-report"></iframe>
</div>
## Getting Started - DCM4CHEE
You can follow the same steps as above to set up DCM4CHEE. The only difference is that you need to navigate to the correct directory. `platform\app\.recipes\Nginx-Dcm4chee-Keycloak`
You can watch the following video, which will guide you through the process of setting up DCM4CHEE.
<div style={{padding:"56.25% 0 0 0", position:"relative"}}>
<iframe src="https://player.vimeo.com/video/981362509?badge=0&amp;autopause=0&amp;player_id=0&amp;app_id=58479" frameBorder="0" allow="autoplay; fullscreen; picture-in-picture" allowFullScreen style= {{ position:"absolute",top:0,left:0,width:"100%",height:"100%"}} title="measurement-report"></iframe>
</div>
## Troubleshooting
_invalid parameter: redirect_uri_
This means the redirect URL isn't set up correctly in the Keycloak client configuration. To resolve this, log in to Keycloak and adjust the settings in the correct client (ohif_viewer) and correct realm (ohif).
_Exit code 137_
This means Docker ran out of memory. Open Docker Desktop, go to the `advanced`
tab, and increase the amount of Memory available.
_Cannot create container for service X_
Use this one with caution: `docker system prune`
_X is already running_
Stop running all containers:
- Win: `docker ps -a -q | ForEach { docker stop $_ }`
- Linux: `docker stop $(docker ps -a -q)`
#### OHIF Viewer
The OHIF Viewer's configuration is imported from a static `.js` file. The
configuration we use is set to a specific file when we build the viewer, and
determined by the env variable: `APP_CONFIG`. You can see where we set its value
in the `dockerfile` for this solution:
`ENV APP_CONFIG=config/docker-nginx-orthanc-keycloak.js`
You can find the configuration we're using here:
`/public/config/docker-nginx-orthanc-keycloak.js`
To rebuild the `webapp` image created by our `dockerfile` after updating the
Viewer's configuration, you can run:
- `docker-compose build` OR
- `docker-compose up --build`
## Next Steps
### Keycloak Theming
The `Login` screen for the `ohif-viewer` client is using a Custom Keycloak
theme. You can find the source files for it in
`platform/app/.recipes/deprecated-recipes/OpenResty-Orthanc-Keycloak/volumes/keycloak-themes`. You can see how
we add it to Keycloak in the `docker-compose` file, and you can read up on how
to leverage custom themes in
[Keycloak's own docs](https://www.keycloak.org/docs/latest/server_development/index.html#_themes).
| Default Theme | OHIF Theme |
| ---------------------------------------------------------------------- | ---------------------------------------------------------------- |
| ![Keycloak Default Theme](../assets/img/keycloak-default-theme.png) | ![Keycloak OHIF Theme](../assets/img/keycloak-ohif-theme.png) |
## Resources
### Referenced Articles
The inspiration for our setup was driven largely by these articles:
- [Securing Nginx with Keycloak](https://edhull.co.uk/blog/2018-06-06/keycloak-nginx)
- [Authenticating Reverse Proxy with Keycloak](https://eclipsesource.com/blogs/2018/01/11/authenticating-reverse-proxy-with-keycloak/)
- [Securing APIs with Kong and Keycloak](https://www.jerney.io/secure-apis-kong-keycloak-1/)
For more documentation on the software we've chosen to use, you may find the
following resources helpful:
- [Orthanc for Docker](http://book.orthanc-server.com/users/docker.html)
- [OpenResty Guide](http://www.staticshin.com/programming/definitely-an-open-resty-guide/)
- [Lua Ngx API](https://openresty-reference.readthedocs.io/en/latest/Lua_Nginx_API/)
- [Auth0: Picking a Grant Type](https://auth0.com/docs/api-auth/which-oauth-flow-to-use)
We chose to use a generic OpenID Connect library on the client, but it's worth
noting that Keycloak comes packaged with its own:
- [oidc-client-js](https://github.com/IdentityModel/oidc-client-js/wiki)
- [Keycloak JavaScript Adapter](https://www.keycloak.org/docs/latest/securing_apps/index.html#_javascript_adapter)
If you're not already drowning in links, here are some good security resources
for OAuth:
- [Diagrams of OpenID Connect Flows](https://medium.com/@darutk/diagrams-of-all-the-openid-connect-flows-6968e3990660)
- [KeyCloak: OpenID Connect Flows](https://www.keycloak.org/docs/latest/securing_apps/index.html#authorization-code)
For a different take on this setup, check out the repositories our community
members put together:
- [mjstealey/ohif-orthanc-dimse-docker](https://github.com/mjstealey/ohif-orthanc-dimse-docker)
- [trypag/ohif-orthanc-postgres-docker](https://github.com/trypag/ohif-orthanc-postgres-docker)
<!--
Links
-->
<!-- prettier-ignore-start -->
<!-- DOCS -->
[orthanc-docs]: http://book.orthanc-server.com/users/configuration.html#configuration
[lua-resty-openidc-docs]: https://github.com/zmartzone/lua-resty-openidc
<!-- SRC -->
[config]: https://github.com/OHIF/Viewers/blob/master/platform/viewer/src/config.js
[dockerfile]: https://github.com/OHIF/Viewers/blob/master/platform/viewer/.recipes/OpenResty-Orthanc-Keycloak/dockerfile
[config-nginx]: https://github.com/OHIF/Viewers/blob/master/platform/viewer/.recipes/OpenResty-Orthanc-Keycloak/config/nginx.conf
[config-orthanc]: https://github.com/OHIF/Viewers/blob/master/platform/viewer/.recipes/OpenResty-Orthanc-Keycloak/config/orthanc.json
[config-keycloak]: https://github.com/OHIF/Viewers/blob/master/platform/viewer/.recipes/OpenResty-Orthanc-Keycloak/config/ohif-keycloak-realm.json
<!-- prettier-ignore-end -->