Third-party health apps integrating with an EHR via OAuth2-scoped FHIR APIs
Before a single API call is made, a third-party developer must register their application in the EHR vendor's public developer portal — a self-service catalog required under federal interoperability rules. Registration is where trust, scope, and identity are first established for an app that will later touch real patient data.
Before a single API call is made, a third-party developer must register their application in the EHR vendor's public developer portal — a self-service catalog required under federal interoperability rules. Registration produces a client_id (a public identifier) and, for confidential clients, a client_secret used to authenticate token requests.
The developer submits an app manifest: the app's name, logo, privacy policy URL, and — critically — the exact list of FHIR scopes it intends to request (e.g., patient/Observation.read, patient/MedicationRequest.read). The manifest also declares one or more redirect URIs, which the authorization server will later validate character-for-character against the URI supplied in the login request, closing a common OAuth phishing vector where an attacker substitutes their own callback endpoint.
Public apps (native mobile, single-page apps with no server-side secret storage) are registered as "public clients" and must use PKCE (Proof Key for Code Exchange) instead of a client secret, since a secret embedded in a mobile binary cannot be kept confidential.
The registration requirement itself is a downstream effect of federal policy. The 21st Century Cures Act (2016) and its 2020 ONC Information Blocking Rule made it illegal for EHR vendors, providers, and health information networks to interfere with the access, exchange, or use of electronic health information — with limited, narrowly defined exceptions.
As a Condition of Certification, ONC-certified EHR technology must publish a standardized, publicly accessible FHIR API "without special effort," meaning any developer can discover the API, read its documentation, and begin a registration flow without a business relationship or custom contract negotiation. Vendors that erect artificial friction — undocumented endpoints, discretionary approval delays, non-standard extensions that break portability — risk OIG enforcement actions carrying penalties up to $1 million per violation for health IT developers.
This is the regulatory backbone that makes an ecosystem of independent third-party health apps possible at all: the API surface is not a vendor favor, it is a compliance obligation.
Since the Information Blocking Rule took effect, ONC-certified EHR developers covering the vast majority of US hospital beds and ambulatory encounters have been required to expose FHIR R4 APIs conformant with USCDI — turning what was once a bespoke integration project into a standardized, discoverable registration flow.
Once registered, a developer's first live interaction with the EHR is almost never a data query — it is discovery. Every FHIR server exposes a CapabilityStatement at the /metadata endpoint: a machine-readable manifest of every resource type the server supports, which interactions (read, search, create) are permitted on each, and which search parameters are indexed.
Alongside it, SMART-enabled servers publish a /.well-known/smart-configuration document listing the authorization and token endpoint URLs, supported scopes, PKCE requirements, and whether the server supports SMART v1 or the more granular SMART v2 scope syntax.
A well-behaved client application parses both documents at startup rather than hardcoding assumptions about a specific vendor's implementation — because while the FHIR resource model is standardized, individual EHR vendors vary in which optional search parameters and extensions they actually implement. Discovery-first design is what lets a single third-party app support many different EHR platforms from one codebase.
The app redirects the patient through the SMART App Launch framework — the standardized handshake that turns a registered client_id into a live, patient-authorized session. Nothing is exchanged until the patient has explicitly seen and approved the exact scopes being requested.
The SMART App Launch framework defines two ways a third-party app can begin a session. In an EHR launch, a clinician clicks the app's icon from inside the EHR itself; the EHR passes a launch context token identifying the current patient and encounter, and the app exchanges it for an access token scoped to that context. In a standalone launch, a patient opens the app directly (e.g., from their phone's home screen) with no pre-existing EHR session; the app must initiate its own authorization request and let the patient select or confirm their identity and applicable patient record.
Both flows converge on the same OAuth 2.0 authorization code grant: the app redirects the browser to the authorization server's /authorize endpoint with its client_id, requested scopes, redirect_uri, and a state parameter for CSRF protection. For public clients — apps with no server-side secret — a PKCE code_verifier/code_challenge pair is added, ensuring that only the party that initiated the flow can redeem the resulting authorization code, even if the code is intercepted.
Before any authorization code is issued, the patient (or an authorized proxy) is shown a consent screen — rendered by the EHR's authorization server, not by the third-party app — enumerating exactly which FHIR resources and access levels are being requested. A well-formed request might read: "MyHealthApp is requesting: read your Observations (labs, vitals), read your Medications, read your Allergies." Nothing broader.
The patient can typically approve the full request, decline it entirely, or in EHRs that support granular consent, deselect individual resource types before continuing. This consent screen is the single most important trust checkpoint in the entire integration: it is the moment a patient exercises informed, resource-level control over their own data, satisfying both HIPAA's authorization principles for non-treatment disclosures and the Cures Act's patient-access mandate.
Once approved, the authorization server redirects back to the app's registered redirect_uri with a short-lived, single-use authorization code.
SMART scopes follow the pattern {context}/{resourceType}.{permission} — for example patient/Observation.read requests read access to Observation resources for the launched patient context. SMART v1 permissions are coarse: .read (any interaction that reads data) or .write.
SMART v2 introduces granular permissions aligned to FHIR's own interaction verbs: .c (create), .r (read), .u (update), .d (delete), .s (search) — combinable, so an app can request patient/Observation.rs (read + search only) without ever being able to create or modify records. V2 also adds explicit support for requesting specific USCDI data classes and "may not exist" hints so an authorization server can prompt a patient sensibly even when a resource type is currently empty.
This granularity directly maps to what a patient sees on the consent screen and what the OAuth gate enforces later — the narrower the requested scope, the smaller the attack surface if the app's credentials are ever compromised.
The authorization code is exchanged for a short-lived access token bound to the granted scopes. From this point forward, every FHIR REST query the app makes is evaluated against that token — not against the app's identity alone — and every access, approved or denied, is written to an immutable audit trail.
The app exchanges its authorization code at the /token endpoint, presenting its client_id, client_secret (or PKCE code_verifier), and the original redirect_uri for verification. On success, the authorization server returns an access token, a token_type (bearer), an expires_in value, the granted scope string (which may be narrower than what was originally requested, if the patient deselected items), and — if offline_access was requested and approved — a refresh token.
Access tokens are deliberately short-lived, typically five minutes to one hour, to limit the exposure window if a token is intercepted or leaked in a log file. Some implementations issue self-contained JWT access tokens carrying the scope and patient context directly in a signed payload the resource server can validate offline; others issue opaque reference tokens that require a live introspection call (RFC 7662) against the authorization server on every API request.
Every subsequent API call — GET /Observation?patient=123&category=laboratory, GET /MedicationRequest?patient=123&status=active — passes the bearer token in the Authorization header. The FHIR resource server extracts the token's granted scopes and patient context before executing any query, and enforces two independent constraints simultaneously: (1) is the requested resource type covered by an approved scope, and (2) does the patient context in the token match the patient parameter in the query.
A request for patient/Condition.read cannot be satisfied with a token scoped only to patient/Observation.read — the server returns 403 Forbidden with an OperationOutcome resource explaining the scope mismatch, not a partial or filtered result. This "deny by default, allow by explicit scope" posture is what prevents scope creep: an app cannot silently begin pulling MedicationRequest data just because it once requested Observation access.
A denied request is not a bug report — it is the scope model working exactly as designed. Every 403 at the OAuth gate is one fewer field of protected health information an over-reaching or compromised app could ever have touched.
When an access token expires mid-session, the app presents its refresh token to the /token endpoint to obtain a new access token without re-prompting the patient — as long as the refresh token itself remains valid and unrevoked. Refresh tokens are long-lived (days to indefinite, per implementation policy) but can be individually revoked by the patient or administrator at any time, immediately invalidating all future token exchanges tied to that grant.
Every resource access — successful or denied — is written to an immutable audit log capturing the actor (client_id and, where available, the authenticated end-user), the granted scope at time of access, the specific resource type and instance ID retrieved, the timestamp, source IP, and response status. This log is the forensic backbone for both HIPAA accounting-of-disclosures requests and later anomaly detection: without a complete record of who accessed what and under which scope, a compromised or over-reaching app cannot be reliably distinguished from normal use.
A patient-facing wellness app and a Carequality-participating enterprise integration are both, technically, "third-party FHIR clients" — but they carry vastly different risk profiles. Production API gateways classify registered apps into trust tiers and enforce differentiated rate ceilings and scope allowances accordingly.
A patient-facing wellness app downloaded from a consumer app store and a Carequality-participating enterprise integration connecting two hospital systems are both, technically, "third-party FHIR clients" — but they carry vastly different risk profiles, and treating them identically would either strangle legitimate high-volume clinical integrations or grant excessive trust to unvetted consumer software.
Most production EHR API gateways therefore classify registered apps into trust tiers. A public patient app authenticates only the individual patient's own consent and typically receives the lowest rate ceiling and the narrowest default scope options. A registered developer with a reviewed business use case and signed data-use agreement receives a moderate ceiling. A certified enterprise integration — one that has completed formal vetting, often participating in a trust framework like Carequality or CommonWell Health Alliance, or operating as a TEFCA Qualified Health Information Network (QHIN) — receives the highest ceilings and the broadest permissible scope grants, because its identity, security posture, and data-use obligations have been independently attested.
The Trusted Exchange Framework and Common Agreement (TEFCA), operationalized by ASTP/ONC and the Sequoia Project as Recognized Coordinating Entity, establishes a single "network of networks" model for nationwide health data exchange. Organizations that meet TEFCA's technical, legal, and governance requirements are designated Qualified Health Information Networks (QHINs) — currently around a dozen — and QHIN participants inherit a baseline of pre-negotiated trust and standardized exchange purposes (treatment, individual access, public health, and more) without needing bilateral agreements with every counterparty.
Carequality and CommonWell Health Alliance operate as complementary, longer-established exchange frameworks bridging over 900 participating health systems and vendor networks, many of which now also participate in TEFCA. An app or organization that can demonstrate participation in one of these frameworks presents a materially stronger trust signal than self-attestation alone — vetting, breach-notification obligations, and technical conformance testing are enforced by the network itself, not left to each individual EHR vendor to independently verify.
Rate limiting is not merely a capacity-management tool — it is a security control. A public app suddenly issuing 500 requests per minute against a 60-request ceiling is either broken, misconfigured, or compromised; the ceiling itself turns anomalous behavior into an immediately observable signal rather than a silent data-exfiltration event.
Trust tier is not merely a label — it caps what an app is even eligible to request. EHR vendors commonly define a "scope ceiling" per tier: public patient apps might be limited to standard USCDI read-only resource classes, while certified enterprise integrations may be approved for write-back interactions (e.g., submitting a CarePlan update or scheduling result) that would never be granted to an unvetted consumer app regardless of what the patient consents to.
Moving up a trust tier typically requires a formal attestation process: submitting security documentation (SOC 2 report, penetration test summary), signing a business associate agreement (BAA) if the app will process PHI on the developer's own infrastructure, and in some cases completing the EHR vendor's own app-review or certification program before the higher rate ceiling and expanded scope catalog are unlocked in the developer portal.
Once an integration is live, the EHR's API gateway becomes clinical infrastructure in its own right. Continuous SLA monitoring, behavioral anomaly detection, and instant, patient-controlled revocation together form the operational backbone that keeps the third-party ecosystem trustworthy after launch day.
Once an integration is live, the EHR's API gateway becomes clinical infrastructure in its own right — a patient-facing medication app or a care-coordination integration failing silently can have real downstream consequences. Production API programs publish and monitor against an uptime SLA, typically 99.9% or higher, tracked via synthetic health-check requests hitting the /metadata and core resource endpoints at fixed intervals from multiple geographic regions.
Dashboards track p50/p95/p99 latency per endpoint, error rate by status code family (4xx client errors vs 5xx server errors), and rolling uptime percentage. A sustained dip is escalated through an incident response process — often the same one used for the EHR's core clinical modules, since the FHIR API is frequently served from the same production infrastructure rather than a segregated sandbox.
Audit logs collected during every scoped query feed a monitoring layer that looks for behavioral patterns inconsistent with legitimate use: a sudden spike in query volume far above an app's historical baseline or trust-tier rate ceiling; sequential enumeration of patient identifiers rather than the expected single-patient-context pattern; access concentrated in an unusual time window; or geographic origin inconsistent with the app's registered infrastructure.
Any of these can indicate bulk data scraping, a compromised client_secret being used outside its intended application, or a credential-stuffing attack against the authorization endpoint itself. Modern gateways apply both static rate/pattern rules and statistical baselining per client_id, flagging deviations for automatic throttling or human security-team review rather than waiting for a scheduled audit to surface the problem weeks later.
When an anomaly is confirmed — or simply when an app is retired, a business relationship ends, or a security incident is declared — the response is immediate credential revocation: the client's active access and refresh tokens are invalidated, and its client_id/client_secret pair can be disabled at the developer-portal level, blocking any future token issuance even with valid credentials.
Critically, this authority is not reserved for administrators. Under the Cures Act's patient-access provisions, patients retain the right to revoke any third-party app's access to their records at any time, directly from the EHR's patient portal — independent of whether the app's developer agrees or is even notified first. Revocation takes effect within seconds: outstanding access tokens are added to a revocation list checked on every request, and any refresh token tied to that grant is permanently invalidated. Continuous compliance auditing then closes the loop, periodically re-verifying that every currently active integration still has valid consent, an unexpired attestation, and a clean anomaly history.
A patient revoking app access is not a support ticket — it is an OAuth-level operation with immediate cryptographic effect: the moment the refresh token is invalidated, the app permanently loses the ability to mint new access tokens, even if it retains a still-valid short-lived token for the remaining seconds of its lifetime.