Dion McMurtrie
Mar 17, 2026, 10:53 AM
I recently noticed a parameter naming collision with SMART apps running against Keycloak 24 which implements RFC 9207. The problem SMART App Launch and RFC 9207 both use a query parameter called iss , but they mean completely different things: SMART App Launch (since ~2014): iss = the FHIR server base URL , passed in the launch URL. e.g. https://app/launch?iss=https://ehr/fhir&launch=xyz123 ( SMART App Launch spec ) RFC 9207 (March 2022): iss = the authorization server's issuer identifier , appended to the OAuth callback URL as a countermeasure against mix-up attacks. e.g. https://app/redirect?code=abc&state=xyz&iss=https://keycloak.example.com/realms/myrealm ( RFC 9207 , specifically Section 2 for the parameter definition and Section 2.4 for client validation requirements) Keycloak added RFC 9207 support in version 23.0.0 (November 2023), enabled by default. Since then, when the authorization server redirects back to the app's redirect_uri , it appends iss=<keycloak issuer URL> to the callback. This can break things in at least two ways - my experience was fhirclient.js (smart-on-fhir/client-js) picking up the Keycloak issuer URL in the redirect and mistake it for the FHIR server, but the extra iss parameter could cause redirect_uri mismatches at the token endpoint if the client doesn't strip it before constructing the token request. There's a relevant Keycloak issue keycloak/keycloak#25684 where users discovered the iss parameter was the root cause and the workaround was to disable it. Current workarounds Keycloak per-client toggle : In the Keycloak Admin Console → Clients → [your SMART client] → Advanced → OpenID Connect Compatibility Modes → enable "Exclude Issuer From Authentication Response". This suppresses the RFC 9207 iss on the callback. Works, but trades away a security feature. Strip iss client-side before calling ready() : In your redirect page, remove the iss param from the URL before the library processes it: const url = new URL ( window . location . href ); url . searchParams . delete ( "iss" ); window . history . replaceState ({}, "" , url . toString ()); FHIR . oauth2 . ready (). then ( client => { ... }); Both of these are band-aids. The first disables a legitimate security mechanism. The second requires every SMART app to add defensive code for something that should be handled at the library or spec level. The bigger question RFC 9207 adoption is only going to increase, it's required by FAPI 2.0 and is showing up in more authorization servers beyond Keycloak. It could be addressed at multiple levels Library fix (client-js) : The library could stop treating iss as meaningful on the callback page. During authorize() , the library reads iss from the launch URL to discover the FHIR server, then stores it in session state. By the time ready() runs on the redirect page, the FHIR server URL is already in session, there's no reason to look at iss in the URL again. Ignoring iss on the callback page would prevent the collision entirely. But still not make use of RFC 9207. Use the RFC 9207 iss for its intended purpose : Rather than just ignoring the iss on the callback, the library could actually validate it. During authorize() , the library already knows which authorization server it redirected the user to (discovered from .well-known/smart-configuration ). When the callback arrives with an RFC 9207 iss , the library could verify it matches the expected authorization server and reject the response if it doesn't. This gives SMART apps the mix-up attack protection that RFC 9207 was designed to provide, while still using the FHIR server URL from session storage for actual FHIR requests. This builds on #1, don't confuse the iss with the FHIR server, and additionally use it as a security check. Spec-level clarification (SMART App Launch) : The SMART spec currently defines iss as the FHIR server URL in launch URLs but says nothing about RFC 9207 using the same parameter name for a different purpose in callback URLs. A note acknowledging the collision and clarifying that iss on a callback is the authorization server issuer, not the FHIR server, would give library authors a clear mandate and put the problem on the radar for other implementers. Has anyone else run into this? I feel like I'm late to this issue since it was implemented in Keycloak so long ago.