# Setting Up the MDM Web UI for Data Stewards

This tutorial walks a system administrator through every step needed before a data steward can open the MDM Web UI dashboard and start reviewing patient identity decisions. The MDM Web UI supports the [Probabilistic MDM](/content/docs/mdm/mdm.html#probabilistic-mdm/index.html) strategy, in which borderline matches are resolved by a human reviewer. For a shorter reference-style overview of the same setup, see the [MDM UI Tutorial](/content/docs/guide_tutorials/tutorial_mdm_ui.html); this tutorial is a detailed step-by-step walkthrough. By the end, the following will be in place:

- An MDM module with match rules that produce both automatic links and **Possible Match** / **Possible Duplicate** tasks for human review
- A SMART Outbound Security module, a JSON Admin API endpoint, and a dedicated FHIR REST endpoint — the three backend services the MDM UI requires
- An OIDC client that authorizes the MDM UI application to authenticate users
- An MDM UI module accessible in a browser
- Data steward user accounts with the correct permissions

MDM is a Premium Solution and requires a separately purchased license. Contact [sales@smiledigitalhealth.com](mailto:sales@smiledigitalhealth.com) if you need to enable it.

# Prerequisites

- A running Smile CDR installation at version 2024.02 or later.
- Access to the Web Admin Console at `http://localhost:9100` (or your configured admin port) using an account with the `ROLE_SUPERUSER` permission.
- The FHIR Storage (R4 RDBMS) module is already created and running. This tutorial refers to it as **persistence**.
- The Subscription Matching module is already created and running with a dependency on **persistence**. On a fresh install both modules exist by default. If either is absent, create it before continuing.

* * *

# Step 1: Configure the FHIR Storage Module

The MDM engine runs asynchronously on top of Smile CDR's subscription infrastructure. Two settings on the FHIR Storage module must be enabled before the MDM module can operate.

**Open the FHIR Storage module**

In the Web Admin Console, go to **Configuration → Module Config** and open the **persistence** module.

**Enable MDM and message subscriptions**

Use the **On this page** sidebar to navigate to each section below, then set the values shown:

| Section in sidebar | Setting | Value |
| --- | --- | --- |
| FHIR MDM Server | MDM Mode Enabled | Enabled |
| FHIR Subscription Persistence | Message Subscription Enabled | Enabled |
| FHIR Configuration | Seed Base Validation Resources | Enabled |

The **Seed Base Validation Resources** setting loads `StructureDefinition` and related FHIR conformance resources on startup. The MDM UI frontend requires these to be present.

**Save and restart the persistence module.**

* * *

# Step 2: Configure the MDM Module with Match Rules

The MDM module processes incoming resources and produces the links that data stewards review. If the match rules produce only `MATCH` and no `POSSIBLE_MATCH` outcomes, the data steward queue will always be empty. This step configures rules that route borderline cases to the dashboard.

**Create the MDM module (if it does not already exist)**

Go to **Configuration → Module Config → Add Module** and select **MDM**.

**Set the consumer thread count**

Set **Consumer Thread Count** to `2` (or `1` for a low-volume environment).

**Set the Subscription dependency**

Under **Module Dependencies**, select the Subscription Matching module.

**Enter the MDM Rule Definition Script**

Paste the following JSON into the **MDM Rule Definition Script** field. It matches automatically when both name and birth date agree, and routes cases where only the family name and birth date agree to the data steward queue as Possible Matches.

```json
{
  "version": "v1",
  "mdmTypes": ["Patient"],
  "candidateSearchParams": [\
    {\
      "resourceType": "Patient",\
      "searchParams": ["birthdate"]\
    },\
    {\
      "resourceType": "Patient",\
      "searchParams": ["identifier"]\
    }\
  ],
  "candidateFilterSearchParams": [],
  "matchFields": [\
    {\
      "name": "family-name",\
      "resourceType": "Patient",\
      "resourcePath": "name.family",\
      "similarity": {\
        "algorithm": "JARO_WINKLER",\
        "matchThreshold": 0.88\
      }\
    },\
    {\
      "name": "given-name",\
      "resourceType": "Patient",\
      "resourcePath": "name.given",\
      "matcher": {\
        "algorithm": "DOUBLE_METAPHONE"\
      }\
    },\
    {\
      "name": "birthdate",\
      "resourceType": "Patient",\
      "resourcePath": "birthDate",\
      "matcher": {\
        "algorithm": "DATE"\
      }\
    }\
  ],
  "matchResultMap": {
    "family-name,given-name,birthdate": "MATCH",
    "family-name,birthdate": "POSSIBLE_MATCH"
  }
}
```

Copy

The `matchResultMap` is what creates work for data stewards. The `MATCH` entry handles the confident cases automatically. The `POSSIBLE_MATCH` entry catches cases where the family name and birth date agree but the given name comparison fails — for example, when the given name is absent on one record or differs significantly. Each `POSSIBLE_MATCH` link becomes a task in the dashboard.

**Enter a survivorship script**

Survivorship rules control which field values are promoted to the Golden Record when multiple source records are linked, and which values are used when two Golden Records are merged. Without a survivorship script, the Golden Record is left empty after a merge.

Paste the following into the **MDM Survivorship Script** field:

```javascript
function mdmApplySurvivorshipRules(targetRec, goldenRec, transactionContext) {
    var helper = new MdmHelper(Fhir.getContext(), targetRec, goldenRec, transactionContext);
    helper.replaceAll();
}

function mdmApplySurvivorshipRulesOnMergeGoldenResources(fromGoldenRec, toGoldenRec, transactionContext) {
    var helper = new MdmHelper(Fhir.getContext(), fromGoldenRec, toGoldenRec, transactionContext);
    helper.mergeAll();
}
```

Copy

The first function runs whenever a source record is linked to a Golden Record: it copies all demographic fields from the source record to the Golden Record. The second function runs when two Golden Records are merged by a data steward: it combines all fields from both records into the surviving one.

**Save the MDM module and start it.**

Verify that the module status icon turns green. If it stays red or yellow, check **Logs → Module Logs** for errors. The most common cause is a missing Subscription Matching dependency.

* * *

# Step 3: Configure the SMART Outbound Security Module

The MDM UI authenticates users through SMART/OIDC. A SMART Outbound Security module issues tokens, and a callback script within it assigns each user the appropriate MDM UI role at login time.

**Create the module (if it does not already exist)**

Go to **Configuration → Module Config → Add Module** and select **SMART Outbound Security**. Use the module ID `smart_auth`.

**Set the listener and issuer**

| Setting | Value |
| --- | --- |
| OpenID Connect (OIDC) → Issuer URL | `http://localhost:9200/smartauth` |
| HTTP Listener → Listener Port | `9200` |
| HTTP Listener → Context Path | `/smartauth` |
| HTTP Listener → Respect Forward Headers | Disabled |
| HTTP Listener → HTTPS Forwarding Assumed | Disabled |

**Reverse proxy deployments only:** If the MDM UI is exposed through an HTTPS-terminating reverse proxy (nginx, Apache, a load balancer, etc.), enable both **Respect Forward Headers** and **HTTPS Forwarding Assumed** on this module. With those flags off, the server generates `http://` URLs for all OIDC endpoints, which is correct for a direct connection. With them on, the server treats every connection as HTTPS and generates `https://` URLs regardless of the actual listener protocol — connecting directly to the server without a proxy in front will then produce an `ERR_SSL_PROTOCOL_ERROR` in the browser.

**Disable enforced scope restrictions**

Under **SMART Authorization**, disable **Enforce Approved Scopes**. This lets the MDM UI receive all the permissions it needs without requiring each scope to be pre-approved on the client.

**Enable CORS**

Under **Cross-Origin Resource Sharing**, enable CORS and set the allowed origin to the URL where the MDM UI will be served (for example, `http://localhost:8080`). Do not use `*` here because the OIDC logout flow requires an exact origin match.

**Add the Post-Authorization Script**

In the **SMART Callback Script** section, paste the following into the **Post Authorization Script Text** field. This script assigns the Data Steward or Admin role to a user based on their username at the time a token is issued.

```javascript
function onTokenGenerating(theUserSession, theAuthorizationRequestDetails) {
    var userName = theUserSession.username;

if (userName === 'DATA_STEWARD' || userName === 'ADMIN') {
        theUserSession.addAuthority('FHIR_WRITE_ALL_OF_TYPE', 'AuditEvent');
        theUserSession.addAuthority('FHIR_WRITE_ALL_OF_TYPE', 'Task');
        theUserSession.addAuthority('ACCESS_ADMIN_JSON');
        theUserSession.addAuthority('UPDATE_USER');
        theUserSession.addAuthority('VIEW_USERS');
    }

if (userName === 'DATA_STEWARD') {
        theUserSession.addAuthority('ROLE_MDMUI_DATASTEWARD_FHIR');
        theUserSession.addAuthority('VIEW_MODULE_CONFIG_FOR_MODULE', 'Master/mdm');
        theAuthorizationRequestDetails.addAccessTokenClaim('user_role', 'data_steward');

} else if (userName === 'ADMIN') {
        theUserSession.addAuthority('FHIR_EXTENDED_OPERATION_ON_SERVER', '$mdm-evaluate');
        theUserSession.addAuthority('ROLE_MDMUI_ADMIN_FHIR');
        theUserSession.addAuthority('MODULE_ADMIN_FOR_MODULE', 'Master/mdm');
        theUserSession.addAuthority('VIEW_BATCH_JOBS');
        theAuthorizationRequestDetails.addAccessTokenClaim('user_role', 'admin');
    }
}
```

Copy

This script uses hard-coded usernames for simplicity. In a production environment, replace the username checks with lookups against your organization's directory service (LDAP or an external OIDC provider) so that role assignment is managed centrally rather than in this script. See the [Roles & Permissions](/content/docs/security/roles_and_permissions.html) reference for the full list of MDM UI authorities.

The `ROLE_MDMUI_DATASTEWARD_FHIR` permission bundle includes the individual MDM operation authorities needed to query links, update links, merge Golden Records, and mark pairs as not-duplicate. The `ROLE_MDMUI_ADMIN_FHIR` bundle includes all of those plus the ability to run `$mdm-clear` and `$mdm-submit`.

**Save and restart the smart\_auth module.**

* * *

# Step 4: Configure the JSON Admin API Endpoint

The MDM UI uses the JSON Admin API to fetch module configuration and metadata.

**Create the module (if it does not already exist)**

Go to **Configuration → Module Config → Add Module** and select **JSON Admin API**.

**Configure it**

| Setting | Value |
| --- | --- |
| HTTP Listener → Listener Port | `9000` |
| HTTP Listener → Context Path | `/json-admin` |
| Auth: OpenID Connect → OpenIdConnect Security | Enabled |
| Cross-Origin Resource Sharing → CORS Enabled | Enabled |
| Cross-Origin Resource Sharing → CORS Allowed Origins | `http://localhost:8080` (or `*` for testing only) |

**Set module dependencies**

In the **Module Dependencies** section, set the **OpenID Connect Authentication** dropdown to `smart_auth`.

**Save and restart the JSON Admin API module.**

* * *

# Step 5: Configure the FHIR REST Endpoint

The MDM UI reads and writes FHIR resources — Patient records, AuditEvents, Tasks — through a dedicated FHIR REST endpoint that is protected by OIDC.

**Create the module**

Go to **Configuration → Module Config → Add Module** and select **FHIR REST Endpoint (All FHIR Versions)**. Use the module ID `fhir_endpoint`.

**Configure it**

| Setting | Value |
| --- | --- |
| HTTP Listener → Listener Port | `8000` |
| HTTP Listener → Context Path | `/fhir-request` |
| FHIR REST Endpoint → Fixed Value for Endpoint Base URL | `http://localhost:8000/fhir-request` |
| Auth: OpenID Connect → OpenID Connect Security | Yes |
| Cross-Origin Resource Sharing → CORS Enabled | Enabled |
| CORS Allowed Origins | `http://localhost:8080` (or `*` for testing only) |

**Set module dependencies**

Under **Module Dependencies**, add:

| Dependency type | Select |
| --- | --- |
| FHIR Storage Module | persistence (FHIR Storage R4 RDBMS) |
| OpenID Connect Authentication | smart\_auth (SMART Outbound Security) |

**Save and start the fhir\_endpoint module.**

* * *

# Step 6: Create the OIDC Client

The MDM UI is a browser application. An OIDC client record authorizes it to request tokens from the SMART auth module.

**Open the OIDC client manager**

In the Web Admin Console, go to **Users & Authorization → OpenID Connect Clients** and click **Add Client**.

**Fill in the client details**

| Field | Value |
| --- | --- |
| Client ID | `MDM_UI` |
| Client Name | `MDM UI` |
| Authorized Grant Types | `Authorization Code`, `Refresh Token`, `JWT Bearer Token` |
| Authorized Redirect URLs | `http://localhost:8080/mdm-ui/` |
| Scopes | `cdr_all_user_authorities``launch/patient``launch/practitioner``offline_access``openid``profile` |

The Authorized Redirect URL must match the URL of the MDM UI module exactly, including the trailing slash and any context path. If you change the MDM UI's port or context path in the next step, update this URL to match.

**Click Create Client.**

* * *

# Step 7: Add the MDM UI Module

**Create the module**

Go to **Configuration → Module Config → Add Module** and select **MDM UI**.

**Configure it**

| Setting | Value |
| --- | --- |
| HTTP Listener → Listener Port | `8080` |
| HTTP Listener → Context Path | `/mdm-ui/` |
| Organization Identifier | A system and value identifying your organization (for example, `http://example.org/org-id\|my-org`). A new Organization resource is created automatically if one does not exist with this identifier. |
| JSON Admin URL | `http://localhost:9000/json-admin` |
| OIDC Client ID | `MDM_UI` |
| Issuer URL | `http://localhost:9200/smartauth` |
| Redirect URL | `http://localhost:8080/mdm-ui/` |
| Logout URL | `http://localhost:9200/smartauth/logout?cb=none&revoke=token&revoke=token_refresh` |

**Set module dependencies**

Under **Module Dependencies**, select all available dependencies. This typically includes the FHIR REST endpoint, the JSON Admin API, and the SMART Outbound Security module.

**Save and start the MDM UI module.**

The MDM UI is now accessible at `http://localhost:8080/mdm-ui/`.

* * *

# Step 8: Create Data Steward User Accounts

Users logging into the MDM UI must have accounts in the local user store. The post-authorization script from Step 3 grants the correct MDM UI permissions at login time based on the username — so the account names must match the values checked in that script.

**Open the User Manager**

Go to **Users & Authorization → User Management** and click **Add User**.

**Create the Data Steward account**

| Field | Value |
| --- | --- |
| Username | `DATA_STEWARD` |
| Password | Choose a strong password and share it securely with the user. |
| Permissions | No explicit permissions are required here — the post-authorization script adds them when the user logs into the MDM UI. |

Click **Save**.

**Create the Admin account (optional)**

Repeat the above with username `ADMIN` if you also want an MDM UI administrator account that can run match evaluations and batch jobs from within the UI.

The hard-coded usernames `DATA_STEWARD` and `ADMIN` match the values in the post-authorization script written in Step 3. If you use different usernames, update the script accordingly. For production deployments with many stewards, replace the per-username checks with group membership lookups so you do not need to modify the script each time you add or remove a user.

* * *

# Step 9: Verify the Setup

**Check module status**

In the Web Admin Console, go to **Configuration → Module Config** and confirm that all seven modules show a green status icon:

- persistence (FHIR Storage R4 RDBMS)
- subscription (Subscription Matching)
- MDM
- smart\_auth (SMART Outbound Security)
- admin\_json (JSON Admin API)
- fhir\_endpoint\_mdm (FHIR REST Endpoint)
- MDM UI

**Log in as a data steward**

Open `http://localhost:8080/mdm-ui/` in a browser. You should be redirected to the SMART auth login page. Log in with the `DATA_STEWARD` credentials created in Step 8.

After login you should see the MDM dashboard with **Possible Matches** and **Possible Duplicates** sections. Both counts will be zero on a fresh installation because no patient data has been loaded yet.

**Confirm the data steward's view**

Verify that the user can see the review queues but does not see the configuration or administration panels — those are visible only to users with the `ADMIN` role.

**Send a test patient pair**

To verify the full pipeline end to end, create two Patient records whose names and birth dates trigger a `POSSIBLE_MATCH` under the rules in Step 2.

Patient 1:

```json
{
  "resourceType": "Patient",
  "name": [{ "family": "Smith", "given": ["Jonathan"] }],
  "birthDate": "1985-03-15"
}
```

Copy

Patient 2 (given name absent — should produce a Possible Match, not an automatic Match):

```json
{
  "resourceType": "Patient",
  "name": [{ "family": "Smith" }],
  "birthDate": "1985-03-15"
}
```

Copy

Wait a few seconds for asynchronous MDM processing, then reload the MDM UI dashboard. The **Possible Matches** count should increment to `1`. The data steward can now open that task and review it.

* * *

# What You've Accomplished

In this tutorial you:

1. Enabled MDM mode and message subscriptions on the FHIR Storage module.
2. Configured the MDM module with match rules that produce both automatic `MATCH` links and `POSSIBLE_MATCH` tasks for data steward review, and a survivorship script for Golden Record merges.
3. Set up a SMART Outbound Security module with a post-authorization script that assigns the `ROLE_MDMUI_DATASTEWARD_FHIR` and `ROLE_MDMUI_ADMIN_FHIR` permission bundles at login.
4. Configured a JSON Admin API endpoint and a FHIR REST endpoint as backend services for the MDM UI.
5. Created an OIDC client that authorizes the MDM UI application.
6. Launched the MDM UI module and confirmed it is reachable in a browser.
7. Created local user accounts for data stewards.

# Next Steps

- **Tuning match rules**: The rules in Step 2 are a starting point. Adjust the `JARO_WINKLER` threshold and the `candidateSearchParams` to fit your patient population's data quality. See [Advanced Rule Authoring](/content/docs/guide_tutorials/advanced_rule_authoring.html) for a systematic guide.
- **Integrating an external identity provider**: Replace the hard-coded username checks in the post-authorization script with LDAP or federated OIDC lookups. See the optional external IdP step in [MDM UI Setup Guide](/content/docs/guide_tutorials/tutorial_mdm_ui.html).
- **Production message broker**: The default embedded message broker is suitable for testing. For production, configure an external Apache Kafka or ActiveMQ broker to ensure MDM processing is durable and scalable. See [Message Broker](/content/docs/guide_installation/message_broker.html).
- **Reverse proxy configuration**: If the MDM UI is exposed through a web proxy, enable **Respect Forward Headers** and **HTTPS Forwarding Assume** on the smart\_auth module, and ensure all four services (MDM UI, FHIR endpoint, SMART auth, JSON Admin API) are reachable at their configured external URLs.
- **Data steward onboarding**: Point your data stewards at the [MDM UI Tutorial](/content/docs/guide_tutorials/tutorial_mdm_ui.html), which describes the review workflow they will follow once the queue has items in it.

[MDM Advanced Rule Authoring Tutorial](/content/docs/guide_tutorials/advanced_rule_authoring.html)

MDM Web UI Admin Setup Tutorial

Tutorials

[Introduction](/content/docs/guide_tutorials/tutorial_intro.html)
[Integration Testing Tutorial](/content/docs/guide_tutorials/tutorial_integration_test.html)
[Custom Operations Tutorial](/content/docs/guide_tutorials/tutorial_custom_operations.html)
[HL7 v2.x Ingestion Tutorial](/content/docs/guide_tutorials/tutorial_processing_hl7_v2x_feeds.html)
[Federated OAuth2/OIDC Tutorial](/content/docs/guide_tutorials/tutorial_federated_oauth2.html)
[MDM Tutorial](/content/docs/guide_tutorials/tutorial_mdm.html)
[MDM UI Tutorial](/content/docs/guide_tutorials/tutorial_mdm_ui.html)
[MDM First Golden Resource Tutorial](/content/docs/guide_tutorials/first_golden_resource.html)
[MDM Advanced Rule Authoring Tutorial](/content/docs/guide_tutorials/advanced_rule_authoring.html)
[MDM Web UI Admin Setup Tutorial](/content/docs/guide_tutorials/mdm_web_ui_admin_setup.html)
[CDA Import Tutorial](/content/docs/guide_tutorials/tutorial_cda_import.html)
[CDA Export Tutorial](/content/docs/guide_tutorials/tutorial_cda_export.html)
[CDA Export - Custom Narrative Generation Tutorial](/content/docs/guide_tutorials/tutorial_cda_export_narrative_generation.html)
[CDA Import JSON example](/content/docs/guide_tutorials/tutorial_cda_import_json.html)
[CDA Export Template JSON example](/content/docs/guide_tutorials/tutorial_cda_export_json.html)
[Consent Tutorial](/content/docs/guide_tutorials/tutorial_consent.html)
[CMS-0057-F IG Setup Tutorial](/content/docs/guide_tutorials/tutorial_terminology_cms57f.html)

[CDA Import Tutorial](/content/docs/guide_tutorials/tutorial_cda_import.html)
