Skip to content

Distinguish RFC 9207 iss from SMART launch iss on OAuth callbacks#205

Open
dionmcm wants to merge 1 commit into
smart-on-fhir:masterfrom
dionmcm:fix-rfc9207-iss-collision
Open

Distinguish RFC 9207 iss from SMART launch iss on OAuth callbacks#205
dionmcm wants to merge 1 commit into
smart-on-fhir:masterfrom
dionmcm:fix-rfc9207-iss-collision

Conversation

@dionmcm

@dionmcm dionmcm commented Mar 20, 2026

Copy link
Copy Markdown

Summary

  • The iss query parameter has two conflicting meanings. SMART App Launch uses it for the FHIR server base URL on launch, while RFC 9207 uses it for the authorization server issuer on OAuth callbacks. Auth servers like Keycloak include RFC 9207 iss by default, which breaks apps when the library mistakes it for the FHIR server URL.
  • As a solution, this change makes callback mode ignore URL iss for FHIR server selection (use stored state instead), and validate it against the discovered issuer when available (implementing RFC 9207), and strips it from the browser URL.
  • Also added issuer field to WellKnownSmartConfiguration, OAuthSecurityExtensions, and ClientState types to support RFC 9207 validation.

Behavioral changes

Scenario Before After
Callback URL has code + RFC 9207 iss iss mistakenly used as FHIR server URL iss ignored for FHIR server; validated against discovery issuer if known
Callback URL has code, no iss Works No change
Launch URL has iss, no code iss read as FHIR server URL No change
Multi-config authorize() called on callback URL Confusing error or wrong config match Clear error directing to init()/ready()
Server publishes issuer in .well-known, callback iss mismatches Silently accepted Throws RFC 9207 issuer mismatch error

Limitations

  • RFC 9207 issuer validation only works when the server publishes issuer in .well-known/smart-configuration. Conformance-only servers (no well-known) skip validation gracefully since the expected issuer is unknown.
  • The iss URL cleanup in ready() is gated behind replaceBrowserHistory (the default). Apps with replaceBrowserHistory: false retain iss in the URL bar, consistent with existing code/state behavior under that setting.

Tests

  • Callback with code + RFC 9207 iss uses stored FHIR server URL, not callback iss
  • Callback with code and no iss still works (regression)
  • Launch URL with iss and no code reads iss as FHIR server URL (regression)
  • Multi-config authorize() on callback throws clear error
  • RFC 9207 issuer validation passes when iss matches discovered issuer
  • RFC 9207 issuer validation fails on mismatch with clear error
  • init() end-to-end flow handles RFC 9207 iss on callback correctly
  • All 427 tests pass (7 new, 420 existing unchanged)

References

This relates to this PR on the SMART on FHIR specification HL7/smart-app-launch#419 (Jira change request) and this Zulip thread.

The iss query parameter has two conflicting meanings: SMART App Launch uses it for the FHIR server base URL on launch, while RFC 9207 uses it for the authorization server issuer on OAuth callbacks. Auth servers like Keycloak include RFC 9207 iss by default, which breaks apps when the library mistakes it for the FHIR server URL.
Use the presence of `code` to detect callback mode. In callback mode, ignore URL iss for FHIR server selection (use stored state instead), validate it against the discovered issuer when available, and strip it from the browser URL to prevent leakage into subsequent authorize calls.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant