# Security

The Zato settings that match each way a DHIS2 instance authenticates clients and calls other systems.

Each section of this page starts from a way the DHIS2 side has been set up and gives the Zato setting that matches it. Calls from Zato to DHIS2 authenticate with a token or with a username and password. Calls from DHIS2 to Zato arrive as event hooks or through the Route API, and each one reaches a REST channel with a security definition of its own.

## A personal access token {#a-personal-access-token}

A personal access token is created in DHIS2 for the user the integration acts as, and Zato sends it in the `Authorization` header with the `ApiToken` prefix. This is a static [bearer token definition](https://zato.io/docs/security/bearer-tokens/index.html), created as described in [the tutorial](https://zato.io/docs/dev/healthcare/dhis2/tutorial.html#step-1-the-security-definition), and every REST outgoing connection to the instance uses it.

A token has an expiry date set when it was created, and DHIS2 responds with HTTP 401 to every request made with a token that has expired. A new token replaces the old one in the definition, and the connections that use the definition send the new token from the next request on, with no restarts and no services deployed again:

1. In the Dashboard, open **Security** ▹ **Bearer tokens**.
2. Click **Edit** in the row of the definition.
3. Paste the new token into **Token** on the **Static tokens** tab.
4. Click **OK**.

With [enmasse](https://zato.io/docs/admin/enmasse.html), the token is the `static_token` of the definition, set through an environment variable as the [enmasse reference](https://zato.io/docs/admin/enmasse-reference.html#bearer_token) shows. A new token is then a new value of the variable and another import of the same file.

## A username and password {#a-username-and-password}

A DHIS2 user's username and password authenticate with HTTP basic auth. The matching Zato setting is a [basic auth definition](https://zato.io/docs/security/basic-auth.html), selected as the **Security** of the REST outgoing connections in place of a bearer token definition:

1. In the Dashboard, open **Security** ▹ **Basic Auth**.
2. Click **Create a Basic Auth definition**.
3. Enter `DHIS2` as the **Name**, and the DHIS2 username and password.
4. Click **OK**.

[Video: DHIS2 username and password](https://zatosource-production.b-cdn.net/docs/gfx/dhis2/security-basic-auth-create.webm?v=1791212128)

Dashboard menu: Security > Basic Auth

The services do not change, because they invoke the connections by name and the connection decides which credentials go with each request.

## Event hooks {#event-hooks}

An event hook makes DHIS2 send a JSON document to a URL each time metadata is created, updated or deleted, or a scheduled job such as the analytics table update runs. The document carries the fields the hook was set up to select, `id` and `displayName` unless it selects others.

The URL is a [REST channel](https://zato.io/docs/dev/rest/channels.html) in Zato, and the credentials the hook sends decide its security definition:

| The hook sends | The channel's security |
| --- | --- |
| A username and password, the `http-basic` authentication of the hook | A [basic auth definition](https://zato.io/docs/security/basic-auth.html) with the same username and password |
| A header with a key, set among the hook's headers | An [API key definition](https://zato.io/docs/security/api-keys.html) whose **Header** is the name of that header |

[Video: Channel for DHIS2 event hooks](https://zatosource-production.b-cdn.net/docs/gfx/dhis2/security-event-hooks-channel-create.webm?v=1791212154)

Dashboard menu: Connections > Channels > REST

The service receives the document as its request:

```python
# -*- coding: utf-8 -*-

# Zato
from zato.server.service import Service

class ReceiveEventHook(Service):
    name = 'dhis2.receive-event-hook'

    def handle(self) -> 'None':

        event = self.request.payload
        self.logger.info('DHIS2 event: %s', event)
```

DHIS2 delivers hooks on a best-effort basis, with no guarantee that each event arrives, so a service that keeps a copy of metadata in step with DHIS2 also reads the full state at an interval.

## The Route API {#the-route-api}

A DHIS2 web app reaches other systems through a route, which DHIS2 runs on the app's behalf so that the app never holds the target's credentials. When the target is Zato, the route points to a REST channel, and the route's authentication decides the channel's security definition:

| The route's authentication | The channel's security |
| --- | --- |
| `http-basic` | A basic auth definition with the same username and password |
| `api-headers` | An API key definition whose **Header** is the header the route sends |

DHIS2 adds the `X-Forwarded-User` header to every request a route sends. It holds the username of the DHIS2 user who made the call from the app, and the service reads it with the other request headers:

```python
# -*- coding: utf-8 -*-

# Zato
from zato.server.service import Service

class RouteLookup(Service):
    name = 'dhis2.route.lookup'

    def handle(self) -> 'None':

        # The DHIS2 user who made the call from the app ..
        headers = self.request.http.headers
        user = headers['x-forwarded-user']

        # .. is the one the request is recorded under.
        self.logger.info('Route request from DHIS2 user %s', user)
```

Header names in `self.request.http.headers` are in lower case. A route on current DHIS2 versions waits five seconds for the channel's response unless it sets a different timeout, and returns an error to the app when the response takes longer.

## Case data in the audit log {#case-data-in-the-audit-log}

Tracker requests and responses carry case data - names, identifiers, phone numbers and results. The [audit log](https://zato.io/docs/admin/audit-log/index.html) records the requests a REST channel receives and the requests an outgoing REST connection sends, with their bodies, and keeps them for 30 days by default. Opening a message body in the Dashboard is itself recorded, with who opened it and when.

The audit log is set per connection and per channel:

1. **Keep the events and the bodies.** This is the default. The retention period applies to both.
2. **Keep the events, expire the bodies sooner.** `Zato_Audit_Log_Content_Retention_Days_REST_OUTGOING` and `Zato_Audit_Log_Content_Retention_Days_REST_CHANNEL` set how many days bodies are kept, as described in [keeping payloads for less time than events](https://zato.io/docs/admin/audit-log/index.html#keeping-payloads-for-less-time-than-events). Bodies of failed requests are kept for the full period, because they are what explains the failure.
3. **Record nothing.** The **Audit log** checkbox in the form of the connection or channel turns its audit log off.

The [OpenTelemetry export](https://zato.io/docs/admin/audit-log/opentelemetry.html) of the audit log sends no bodies unless an object's payload export flag is turned on.

## See also {#see-also}

- [Bearer tokens](https://zato.io/docs/security/bearer-tokens/index.html) - Static tokens sent in a header of your choice
- [Basic Auth](https://zato.io/docs/security/basic-auth.html) - Usernames and passwords for channels and outgoing connections
- [API keys](https://zato.io/docs/security/api-keys.html) - Keys in a header, matched before any service code runs
- [Audit log](https://zato.io/docs/admin/audit-log/index.html) - What is recorded, for how long, and who opened it

## Learn more {#learn-more}

- [Healthcare interface engine](https://zato.io/docs/dev/healthcare/) - Clinical messages, FHIR, conversion and operations
- [HL7 v2 parsing](https://zato.io/docs/dev/healthcare/hl7/v2/parsing/) - Messages as typed objects, with field access and validation
- [MLLP channels](https://zato.io/docs/dev/healthcare/hl7v2/mllp/) - Receiving and sending HL7 v2 over MLLP sockets
- [FHIR integrations](https://zato.io/docs/dev/healthcare/hl7/fhir/) - Read and write FHIR resources, with paths, bundles and extensions
- [HL7 v2 to FHIR](https://zato.io/docs/dev/healthcare/hl7/to-fhir/) - Converting v2 messages into FHIR bundles, codes and references included
- [EDIFACT](https://zato.io/docs/dev/healthcare/edifact/) - Parsing interchanges, dialects and the transports they arrive on
- [Transformation](https://zato.io/docs/dev/healthcare/transformation/) - One message in, another standard out, in ordinary Python
- [Audit log](https://zato.io/docs/dev/healthcare/audit-log.html) - Every message with its acknowledgment, searchable by patient identifier
- [AI in clinical interfaces](https://zato.io/docs/dev/healthcare/ai/) - Services calling LLMs and AI agents calling clinical services as tools
