# HL7 FHIR security

Authenticate to FHIR servers with Basic Auth, OAuth bearer tokens or a SMART Backend Services private key - assign a definition once and every request is sent with the credentials.

To authenticate to a FHIR server, assign a security definition to the connection's **Security** field - Zato attaches the credentials to every request the connection makes. Which definition to create depends on what the server requires:

- **Basic Auth** - a username and password, allocated in advance by the server's maintainers
- **Bearer tokens** - a client ID with a secret or a private key that Zato exchanges for short-lived OAuth tokens with the server's authorization service, the private key being what SMART Backend Services and the EHRs behind them call for

The two are mutually exclusive on one connection - HTTP reserves a single Authorization header for credentials. TLS is independent of either and applies when the connection's address starts with `https://`.

## Basic Auth {#basic-auth}

To create the credentials, go to **Security** > **Basic Auth** in the Dashboard, click **Create a Basic Auth definition** and fill in the form:

1. **Name:** FHIR.Credentials
2. **Username:** the username the server's maintainers allocated
3. **Password:** the matching password
4. Click **OK**

![New Basic Auth definition](https://zatosource-production.b-cdn.net/docs/gfx/security/fhir-basic-auth-create.webp?v=1790440724)

Dashboard menu: Security > Basic Auth

Then select the definition in the **Security** field of a new or existing [FHIR connection](https://zato.io/docs/dev/healthcare/hl7/fhir/connections/index.html) - the change takes effect as soon as you click OK, with no restarts:

![FHIR connection with security](https://zatosource-production.b-cdn.net/docs/gfx/outgoing/fhir-connection-security.webp?v=1791135898)

Dashboard menu: Connections > Outgoing > HL7 > FHIR

## OAuth bearer tokens {#oauth-bearer-tokens}

With OAuth, your Zato server holds a client ID and secret, exchanges them for a short-lived token with the server's authorization service, and sends that token in the Authorization header. The token is scoped - it grants access to the subset of the FHIR API the definition's scopes name.

Zato acquires and refreshes the tokens for you. When a token expires, the next request obtains a fresh one from the authorization service - no configuration and no code are required for the renewal.

To create the definition, go to **Security** > **Bearer tokens** in the Dashboard, click **Create a Bearer token definition** and fill in the form:

1. **Name:** FHIR.Identity
2. **Auth. endpoint:** the token endpoint, e.g. `https://identity.hospital.example.com/oauth/token`
3. **Client ID:** the client ID the authorization service issued
4. **Client secret:** the matching client secret
5. (Optional) **OAuth scopes:** the scopes the FHIR server expects, e.g. `system/*.read`
6. Click **OK**

![New Bearer token definition](https://zatosource-production.b-cdn.net/docs/gfx/security/fhir-bearer-token-create.webp?v=1790934272)

Dashboard menu: Security > Bearer tokens

Then select the definition in the connection's **Security** field, as described for [Basic Auth](https://zato.io/docs/security/basic-auth.html). To learn about every field of the definition, JWT verification and identity provider walkthroughs, see [Bearer tokens](https://zato.io/docs/security/bearer-tokens/index.html).

### SMART Backend Services with a private key {#smart-backend-services-with-a-private-key}

Epic, Oracle Health, eClinicalWorks and the other certified EHRs let backend applications log in without a client secret, under the name SMART Backend Services. Your Zato server holds a private key, signs each token request with it and the EHR verifies the signature with the public key you registered - no secret is ever sent over the wire, and the EHR never holds anything that could be used to impersonate your application.

The same bearer token definition covers it, with **Authentication** switched to **Private key JWT**:

1. **Name:** FHIR.Identity
2. **Auth. endpoint:** the EHR's token URL - it is the `token_endpoint` of the server's `.well-known/smart-configuration` document, e.g. `https://fhir.epic.com/interconnect-fhir-oauth/oauth2/token`
3. **Client ID:** the client ID the EHR issued when the application was registered
4. **Authentication:** Private key JWT
5. **Private key:** the PEM private key - generate one with `openssl genrsa -out fhir.key 2048`, paste the file's contents here
6. **Algorithm:** `RS384`, as SMART Backend Services require
7. **Key ID:** a name for the key, e.g. `fhir-key-1`
8. **OAuth scopes:** the `system/` scopes the application needs, e.g. `system/Patient.read`
9. Click **OK**

![Bearer token with a private key JWT](https://zatosource-production.b-cdn.net/docs/gfx/security/fhir-bearer-token-private-key-jwt.webp?v=1790934274)

Dashboard menu: Security > Bearer tokens

Once the definition is saved, open it again and follow its **Public key** link - it serves the key's public half as a JWK set, which is the format the EHRs' developer portals take when the application is registered. Epic and Oracle Health pick the verification key by its `kid`, so the Key ID is what ties the registered key to the signed requests.

The connection itself does not change - select the definition in its **Security** field and every request obtains its token with a signed assertion, including a [bulk export](https://zato.io/docs/dev/healthcare/hl7/fhir/bulk-export/index.html) of the connection, which the `system/*.read` scopes above authorize. The [Epic and Oracle Health page](https://zato.io/integrations/healthcare/connect-epic-oracle-fhir-python/) shows the full flow from registration to the first patient read.

## TLS {#tls}

A connection whose address starts with `https://` encrypts all traffic and validates the server's certificate against the publicly trusted certificate authorities - the same set that web browsers use. A certificate that fails the validation stops the request with an error before any data is exchanged.

If the server's certificate comes from an internal, non-public CA, point the `REQUESTS_CA_BUNDLE` environment variable of the Zato server process at the CA's certificate bundle in the PEM format - the connection then validates certificates against that bundle.

## See also {#see-also}

- [Bearer tokens](https://zato.io/docs/security/bearer-tokens/index.html) - Every field of the definition, JWKS and identity providers
- [Connections](https://zato.io/docs/dev/healthcare/hl7/fhir/connections/index.html) - The Dashboard form and everything self.fhir accepts
- [Audit log](https://zato.io/docs/dev/healthcare/audit-log.html) - Every FHIR request and response the connection records

## 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
