§170.315(g)(10)(v) · §170.404(a)(2) · SMART App Launch

SMART on FHIR Authorization

Public technical documentation for the OAuth 2.0 / SMART App Launch authorization that protects the Retinex standardized FHIR API.

Version 1.0 Updated 2026-06-03 Publicly accessible · No charge
Scope. This document describes how an application authenticates and is authorized to access the Retinex FHIR API certified to §170.315(g)(10). It is published as developer-facing technical documentation under §170.315(g)(10)(viii) and the API Transparency conditions at §170.404(a)(2), via a publicly accessible hyperlink with no preconditions.

1. Standards Supported

CapabilityStandard / Value
Authorization frameworkOAuth 2.0 (RFC 6749, RFC 6750)
SMART App LaunchSMART App Launch 1.0.0 and 2.0.0 (SVAP-approved)
FHIR versionHL7® FHIR® R4 (4.0.1)
US Core IGUS Core 6.1.0 (USCDI v3) — see US Core Support
PKCERFC 7636, S256 challenge method (required)
OpenID ConnectOIDC Core 1.0 (id_token, fhirUser claim)
Dynamic Client RegistrationRFC 7591 — see App Registration
Token revocationRFC 7009

Both standalone launch and EHR launch sequences are supported, for patient/, user/, and system/ (backend services, SMART Backend Services) contexts.

2. Authorization Endpoints

The FHIR service base URL (the endpoint against which all interactions in the API Reference are issued) is:

https://retinex-apim.azure-api.net/smart

The associated SMART/OAuth 2.0 endpoints are:

EndpointURL
SMART discoveryhttps://retinex-apim.azure-api.net/smart/.well-known/smart-configuration
Authorizationhttps://retinex-apim.azure-api.net/auth/authorize
Tokenhttps://retinex-apim.azure-api.net/auth/token
Token (refresh)https://retinex-apim.azure-api.net/auth/token
Registration (DCR)https://retinex-apim.azure-api.net/auth/register
Revocationhttps://retinex-apim.azure-api.net/auth/revoke
Capability statementhttps://retinex-apim.azure-api.net/smart/metadata

Always resolve endpoints from discovery, not from this table, so your application keeps working if an endpoint moves.

3. Discovery — .well-known/smart-configuration

A GET on the discovery URL returns the server's authorization metadata. No authentication is required.

GET https://retinex-apim.azure-api.net/smart/.well-known/smart-configuration
Accept: application/json
{
  "issuer": "https://retinex-apim.azure-api.net/smart",
  "authorization_endpoint": "https://retinex-apim.azure-api.net/auth/authorize",
  "token_endpoint": "https://retinex-apim.azure-api.net/auth/token",
  "registration_endpoint": "https://retinex-apim.azure-api.net/auth/register",
  "revocation_endpoint": "https://retinex-apim.azure-api.net/auth/revoke",
  "grant_types_supported": ["authorization_code", "refresh_token", "client_credentials"],
  "response_types_supported": ["code"],
  "code_challenge_methods_supported": ["S256"],
  "token_endpoint_auth_methods_supported": ["none", "client_secret_basic", "private_key_jwt"],
  "scopes_supported": [
    "openid", "fhirUser", "profile", "offline_access", "online_access",
    "launch", "launch/patient",
    "patient/*.rs", "user/*.rs", "system/*.rs"
  ],
  "capabilities": [
    "launch-standalone", "launch-ehr",
    "client-public", "client-confidential-symmetric", "client-confidential-asymmetric",
    "context-standalone-patient", "context-ehr-patient",
    "permission-patient", "permission-user", "permission-v2", "authorize-post",
    "sso-openid-connect", "permission-offline"
  ]
}

4. Authorization Code Flow (with PKCE)

Browser and mobile apps use the Authorization Code grant. PKCE (S256) is required for all clients, public and confidential — this is required for SVAP SMART v2.0.0 and mitigates authorization-code interception.

  1. Generate PKCE values. Create a high-entropy code_verifier (43–128 chars) and derive code_challenge = BASE64URL(SHA256(code_verifier)).
  2. Redirect to the authorization endpoint:
GET https://retinex-apim.azure-api.net/auth/authorize
  ?response_type=code
  &client_id=YOUR_CLIENT_ID
  &redirect_uri=https://app.example.com/callback
  &scope=openid%20fhirUser%20offline_access%20launch/patient%20patient/Patient.rs%20patient/Observation.rs
  &state=RANDOM_STATE
  &aud=https://retinex-apim.azure-api.net/smart
  &code_challenge=CODE_CHALLENGE
  &code_challenge_method=S256
  1. The user authenticates (password + MFA — see MFA) and approves the requested scopes. Retinex redirects back to your redirect_uri with code and state.
  2. Exchange the code at the token endpoint, presenting the original code_verifier:
POST https://retinex-apim.azure-api.net/auth/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
&code=AUTH_CODE
&redirect_uri=https://app.example.com/callback
&client_id=YOUR_CLIENT_ID
&code_verifier=CODE_VERIFIER

Confidential clients additionally authenticate with client_secret_basic (HTTP Basic) or private_key_jwt. Public/native clients send no secret and rely on PKCE.

Token response

{
  "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "openid fhirUser offline_access launch/patient patient/Patient.rs patient/Observation.rs",
  "refresh_token": "rt_8f2b1c9d4a7e...",
  "id_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
  "patient": "example-patient"
}

Send the access token as a Bearer token on every FHIR request: Authorization: Bearer <access_token>.

5. Refresh Tokens & Secure Issuance to Native Applications

Request the offline_access scope (or online_access for session-bound refresh) to receive a refresh_token. Refresh:

POST https://retinex-apim.azure-api.net/auth/token
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token
&refresh_token=REFRESH_TOKEN
&client_id=YOUR_CLIENT_ID

Refresh tokens are rotated on each use (the prior token is invalidated) and are bound to the issuing client.

Method for secure issuance of an initial refresh token to native applications

Per §170.315(g)(10)(v)(A)(1)(iii), Retinex publishes the method by which a refresh token is securely issued to a native application (a "public" client that cannot protect a client secret):

  • Public client + PKCE. Native apps register as public clients with token_endpoint_auth_method = none and obtain tokens using the Authorization Code grant with PKCE S256. No client secret is distributed in the app binary.
  • Authenticated, scoped request. The initial refresh token is issued only after the user completes interactive authentication (password + MFA) and explicitly grants the offline_access scope, over TLS 1.2+.
  • Registered, app-claimed redirect URIs. The redirect URI must exactly match a value registered for the client. Native apps use either an OS-registered private-use URI scheme (e.g. com.example.app:/oauth2redirect) or an HTTPS claimed (App Links / Universal Links) redirect, so the authorization response is delivered only to the legitimate app.
  • On-device secure storage. Applications are expected to store the issued refresh token using platform-provided secure storage — the iOS Keychain or Android Keystore / EncryptedSharedPreferences — never in plaintext.
  • Rotation & revocation. Refresh tokens rotate on use and can be revoked by the user (from their Retinex account) or by the app via the revocation endpoint.

Confidential clients that can protect a secret may instead use client_secret_basic or private_key_jwt; the affirmation of an application's ability to protect a secret is treated in good faith per §170.404(a)(4).

6. PKCE Requirement

Retinex requires PKCE on every Authorization Code request, documented here per §170.315(g)(10)(viii) and §170.404(a)(2):

  • code_challenge_method must be S256 (plain is not accepted).
  • code_verifier: 43–128 characters from the unreserved set [A-Z a-z 0-9 - . _ ~].
  • Authorization requests missing a code_challenge are rejected with invalid_request.
  • Token requests whose code_verifier does not hash to the original code_challenge are rejected with invalid_grant.

7. Supported Scopes

Published per the API Transparency conditions at §170.404(a)(2). Retinex supports SMART v2 (.rs/.cruds) scope syntax and accepts SMART v1 (.read/.write/.*) for backward compatibility.

OpenID & launch context

ScopeMeaning
openid, fhirUser, profileOpenID Connect sign-in; fhirUser claim identifies the authorized user as a FHIR resource.
launchEHR launch — inherit the launch context (patient, encounter).
launch/patientStandalone launch — prompt to select / confirm the patient context.
offline_accessIssue a refresh token usable when the user is offline.
online_accessIssue a refresh token valid only while the user's session is active.

Resource scopes

Format: <context>/<Resource>.<permissions> where context is patient, user, or system, and permissions are r/s (read/search, v2) or read (v1). Wildcards patient/*.rs, user/*.rs, and system/*.rs grant the permission across every supported resource.

Per-resource scopes are offered for each of the 26 US Core 6.1.0 resource types exposed by the API:

patient/AllergyIntolerance.rs   user/AllergyIntolerance.rs   system/AllergyIntolerance.rs
patient/CarePlan.rs             patient/CareTeam.rs          patient/Condition.rs
patient/Coverage.rs             patient/Device.rs            patient/DiagnosticReport.rs
patient/DocumentReference.rs    patient/Encounter.rs         patient/Goal.rs
patient/Immunization.rs         patient/Location.rs          patient/Medication.rs
patient/MedicationDispense.rs   patient/MedicationRequest.rs patient/Observation.rs
patient/Organization.rs         patient/Patient.rs           patient/Practitioner.rs
patient/PractitionerRole.rs     patient/Procedure.rs         patient/Provenance.rs
patient/QuestionnaireResponse.rs patient/RelatedPerson.rs    patient/ServiceRequest.rs
patient/Specimen.rs

(The same list is available under the user/ and system/ contexts. The live API Reference scope picker is generated from this same catalog.)

8. Backend Services (system scopes)

Server-to-server clients use the SMART Backend Services profile: register a confidential asymmetric client with a public key (JWK Set URL), then exchange a signed client_assertion for an access token using grant_type=client_credentials and system/*.rs scopes. No user is present and no refresh token is issued.

9. Token Revocation

POST https://retinex-apim.azure-api.net/auth/revoke
Content-Type: application/x-www-form-urlencoded

token=REFRESH_OR_ACCESS_TOKEN
&token_type_hint=refresh_token
&client_id=YOUR_CLIENT_ID

10. Error Handling

ErrorWhen
invalid_requestMissing/duplicate parameter, or missing PKCE code_challenge.
invalid_clientUnknown client, or failed client authentication.
invalid_grantExpired/used auth code, PKCE verifier mismatch, or revoked refresh token.
invalid_scopeA requested scope is not supported or not granted to the client.
unauthorized_clientThe client is not permitted to use the requested grant type.
access_deniedThe user declined the authorization request.

FHIR API requests with a missing/expired token return 401 Unauthorized; a token lacking the required scope returns 403 Forbidden with an OperationOutcome (see the API Reference).

11. Change Log

DateVersionChange
2026-06-031.0Initial public SMART on FHIR authorization documentation for §170.315(g)(10).