
Server-to-server FHIR authentication uses SMART Backend Services — the machine-to-machine cousin of SMART's patient launch. The spec is well-defined; production implementation has a few edge cases worth understanding.
Client credentials + JWT assertion. The client generates a JWT signed with its private key. The auth server verifies signature against the registered public key and issues an access token. No user-facing consent flow.
Key rotation. JWTs are signed with client private keys. Rotating keys requires updating the registered public JWK. Auth servers should support multiple concurrent JWKs during rotation to avoid downtime.
Scope granularity. Backend services request system/{Resource}.{c|r|u|d} scopes. Fine-grained scopes (system/Observation.rs for read+search) are SMART v2; coarse (system/*.read) is v1. Support both during transition.
Token TTL and refresh. Backend service tokens are typically short-lived (5-15 min) and re-obtained via new JWT assertions. No refresh tokens; each token acquisition is a fresh assertion.
Common integration mistakes
| Mistake | Fix |
|---|---|
| Long-lived tokens | Rotate on 15-min cadence |
Missing aud claim in JWT |
Set to auth server URL |
Wrong iss/sub values |
Both = client_id |
| No JWK rotation support | Register second JWK before rotating |
| Missing kid in JWK header | Auth server can't select key |
Vendor state (mid-2026)
| Auth server | SMART BE Services | Multiple JWKs | v2 scopes |
|---|---|---|---|
| Keycloak | Full | Yes | Yes |
| Auth0 | Full | Yes | Yes |
| Aidbox built-in | Full | Yes | Yes |
| Custom OAuth 2.0 | Depends | Depends | Depends |
SMART Backend Services is the right pattern for FHIR API integrations. Get the JWT assertion and JWK rotation flow right upfront.