When a SMART on FHIR app launches inside an electronic health record, the OAuth 2.0 flow executes in seconds, but the misconfigurations behind that handshake can linger for months. Penetration testers routinely uncover the same handful of weaknesses across every EHR integration they probe, and regulators have started taking notice. This walkthrough reframes the typical pre-launch review as an adversarial exercise, drawing on the patterns testers look for and the remediation steps that close them before an external auditor ever signs in.
Why the SMART Launch Layer Keeps Attracting Attention
SMART on FHIR is deliberately flexible. A single spec supports standalone launches, EHR-launched apps, patient-facing flows, and bulk data export, each with its own token endpoint, redirect behavior, and scope semantics. That flexibility is a feature for developers and a liability for security teams, because the threat model changes depending on which launch style is in play.
Health data is also uniquely valuable on secondary markets, which means EHR integrations attract both opportunistic bug bounty researchers and well-funded adversaries. Recent enforcement actions have shown that regulators treat OAuth misconfigurations in healthcare contexts as compliance failures, not just engineering debt. A pre-launch audit is no longer optional.
The Penetration Tester’s Mental Model
Before running any tooling, experienced testers build a map of the launch surface. They enumerate the registered client IDs, identify whether the app uses the authorization code flow with PKCE or a backchannel variant, and trace the redirect URIs accepted by the authorization server. They also note the FHIR base URL, the scopes requested (including any custom launch or patient-scoped claims), and the token endpoint used to exchange codes.
That map drives every test that follows. Without it, a checklist becomes a box-ticking exercise instead of a targeted review. The goal is to answer one question: what would a motivated attacker do with a single intercepted authorization code, a stolen refresh token, or a malformed redirect?
High-Impact Misconfigurations Testers Look For
Open or Wildcard Redirect URIs
The single most common finding is a redirect URI policy that accepts prefixes, query parameters, or subdomains the developer never intended. A URI like https://app.example.com/callback* is convenient during development and catastrophic in production. Testers attempt to redirect to attacker-controlled paths, open redirects on the same domain, and JavaScript URIs that exfiltrate the authorization code directly to a listener.
Missing or Weak PKCE Enforcement
Public clients on mobile and single-page apps must use PKCE with a high-entropy verifier. Auditors check whether the authorization server actually validates the code challenge and whether the S256 method is required rather than the plaintext “plain” method, which can be downgraded by an attacker who can manipulate the request.
Overbroad Scopes and Missing Audience Binding
Apps that request patient/*.read when they only need Observation.read fail the principle of least privilege. Testers also check whether access tokens are audience-restricted to the FHIR server they are intended for, or whether a token minted for one resource server can be replayed against another tenant in the same identity provider.
Refresh Token Lifetimes and Rotation Gaps
Long-lived refresh tokens without rotation are a common finding, particularly in backend services that cache credentials for batch jobs. Testers look for refresh tokens that remain valid after use, tokens stored in browser-accessible storage, and absence of sender-constrained tokens where the deployment supports DPoP or mTLS.
Smart Style Launch Parameter Mishandling
The launch parameter, the need_patient_banner flag, and the EHR’s selected patient context are all sensitive. Testers probe whether the app trusts the launch value from the browser, whether the patient context is bound to the issued token, and whether the app verifies the aud claim against its own client ID before fetching resources.
A Pre-Audit Checklist for Development Teams
The following sequence mirrors how a penetration tester structures a SMART on FHIR engagement. Running it before release catches the majority of findings that would otherwise show up in a formal audit.
- Enumerate authorized redirect URIs. Confirm the list is exact, uses HTTPS, contains no wildcards, and is validated server-side against the registered value. Reject any redirect that includes user-controlled fragments or open parameters.
- Verify PKCE on every public client. Ensure the authorization server rejects authorization requests lacking a code challenge and rejects the plaintext method. Confirm the verifier is regenerated for every transaction.
- Audit requested scopes. Remove any scope the application does not actively use, including the wildcard
patient/*anduser/*grants. Document the clinical justification for each remaining scope. - Inspect token lifetimes and rotation. Reduce access token lifetimes to the shortest window compatible with the workflow, and ensure refresh tokens are rotated, single-use, and bound to the client.
- Check token audience binding. Verify that the access token’s
audclaim is checked against the FHIR base URL before any resource request, and that tokens are not accepted across multiple tenants. - Review SMART launch context handling. Confirm that the
launchparameter, patient ID, and encounter ID are treated as untrusted until validated against the token’s claims. Reject any token whose context does not match the launch request. - Test token storage on every client platform. Mobile apps should use the platform keystore; web apps should keep tokens in memory or in a backend session, not localStorage. Confirm that no token appears in URLs, referrer headers, or logs.
- Run an authorization server configuration review. Validate discovery document fields, JWKS rotation behavior, and the error responses returned for invalid clients, scopes, and redirect URIs. Testers prefer verbose, structured error messages; production should suppress them.
Where Tooling Helps and Where It Falls Short
Dynamic scanners and SMART-specific test harnesses can surface many of the issues above, but they struggle with launch-context-dependent behavior. A scanner sees a token request and a FHIR call; a tester sees the sequence of context switches, redirect handling, and patient-banner interactions that determine whether the flow is actually safe. Treat tooling as a fast filter, then complete the review with manual testing against a staging environment that mirrors production.
It is also worth running an adversarial tabletop exercise focused on the OAuth flow. Walk through what happens if a registered clinician’s browser is compromised, if a refresh token is leaked through a logging pipeline, or if a malicious patient crafts a deep link into the app’s launch endpoint. These scenarios expose assumptions that no automated scanner can detect.
Closing the Gap Before External Review
Most SMART on FHIR security findings follow predictable patterns, and the patterns are well understood. What separates a smooth external audit from a painful one is whether the development team has already walked the same path a tester would. Building the pre-audit checklist into the release process, treating the OAuth configuration as production code, and revisiting it whenever scopes, redirect URIs, or client types change keeps the launch surface narrow. The result is an integration that holds up under both penetration testing and regulatory scrutiny, without last-minute remediation sprints.
