text: (window.webpackJsonp=window.webpackJsonp||[]).push([[23],{"013z":function(e,t,a){"use strict";var n=a("q1tI"),r=a.n(n),i=a("NmYn"),o=a.n(i),s=a("Wbzz"),c=a("Xrax"),l=a("k4MR"),p=a("TSYQ"),b=a.n(p),h=a("QH2O"),u=a.n(h),d=a("qKvR"),m=function(e){var t,a=e.title,n=e.theme,r=e.tabs,i=void 0===r?[]:r;return Object(d.b)("div",{className:b()(u.a.pageHeader,(t={},t[u.a.withTabs]=i.length,t[u.a.darkMode]="dark"===n,t))},Object(d.b)("div",{className:"bx--grid"},Object(d.b)("div",{className:"bx--row"},Object(d.b)("div",{className:"bx--col-lg-12"},Object(d.b)("h2",{id:"page-title",className:u.a.text},a)))))},f=a("BAC9"),O=function(e){var t=e.relativePagePath,a=e.repository,n=Object(s.useStaticQuery)("1364590287").site.siteMetadata.repository,r=a||n,i=r.baseUrl,o=r.subDirectory,c=i+"/edit/"+r.branch+o+"/src/pages"+t;return i?Object(d.b)("div",{className:"bx--row "+f.row},Object(d.b)("div",{className:"bx--col"},Object(d.b)("a",{className:f.link,href:c},"Edit this page on GitHub"))):null},j=a("FCXl"),g=a("dI71"),v=a("I8xM"),N=function(e){function t(){return e.apply(this,arguments)||this}return Object(g.a)(t,e),t.prototype.render=function(){var e=this.props,t=e.title,a=e.tabs,n=e.slug,r=n.split("/").filter(Boolean).slice(-1)[0],i=a.map((function(e){var t,a=o()(e,{lower:!0,strict:!0}),i=a===r,c=new RegExp(r+"/?(#.*)?$"),l=n.replace(c,a);return Object(d.b)("li",{key:e,className:b()((t={},t[v.selectedItem]=i,t),v.listItem)},Object(d.b)(s.Link,{className:v.link,to:""+l},e))}));return Object(d.b)("div",{className:v.tabsContainer},Object(d.b)("div",{className:"bx--grid"},Object(d.b)("div",{className:"bx--row"},Object(d.b)("div",{className:"bx--col-lg-12 bx--col-no-gutter"},Object(d.b)("nav",{"aria-label":t},Object(d.b)("ul",{className:v.list},i))))))},t}(r.a.Component),y=a("MjG9"),k=a("CzIb");t.a=function(e){var t=e.pageContext,a=e.children,n=e.location,r=e.Title,i=t.frontmatter,p=void 0===i?{}:i,b=t.relativePagePath,h=t.titleType,u=p.tabs,f=p.title,g=p.theme,v=p.description,w=p.keywords,x=Object(k.a)().interiorTheme,R=Object(s.useStaticQuery)("2456312558").site.pathPrefix,A=R?n.pathname.replace(R,""):n.pathname,T=u?A.split("/").filter(Boolean).slice(-1)[0]||o()(u[0],{lower:!0}):"",I=g||x;return Object(d.b)(l.a,{tabs:u,homepage:!1,theme:I,pageTitle:f,pageDescription:v,pageKeywords:w,titleType:h},Object(d.b)(m,{title:r?Object(d.b)(r,null):f,label:"label",tabs:u,theme:I}),u&&Object(d.b)(N,{title:f,slug:A,tabs:u,currentTab:T}),Object(d.b)(y.a,{padded:!0},a,Object(d.b)(O,{relativePagePath:b})),Object(d.b)(j.a,{pageContext:t,location:n,slug:A,tabs:u,currentTab:T}),Object(d.b)(c.a,null))}},BAC9:function(e,t,a){e.exports={bxTextTruncateEnd:"EditLink-module--bx--text-truncate--end--2EvBK",bxTextTruncateFront:"EditLink-module--bx--text-truncate--front--5mCgN",link:"EditLink-module--link--QlquD",row:"EditLink-module--row--104Au"}},Dqqg:function(e,t,a){"use strict";a.r(t),a.d(t,"_frontmatter",(function(){return s})),a.d(t,"default",(function(){return p}));var n=a("wx14"),r=a("zLVn"),i=(a("q1tI"),a("7ljp")),o=a("013z"),s=(a("qKvR"),{}),c={_frontmatter:s},l=o.a;function p(e){var t=e.components,a=Object(r.a)(e,["components"]);return Object(i.b)(l,Object(n.a)({},c,a,{components:t,mdxType:"MDXLayout"}),Object(i.b)("p",null,"By Lee Surprenant "," "," "," | "," "," "," Published March 23, 2021"),Object(i.b)("h3",null,"Background"),Object(i.b)("p",null,"The ",Object(i.b)("a",Object(n.a)({parentName:"p"},{href:"https://ibm.github.io/FHIR"}),"IBM FHIR Server")," implements ",Object(i.b)("a",Object(n.a)({parentName:"p"},{href:"https://www.hl7.org/fhir/R4"}),"HL7 FHIR version 4.0.1 (R4)")," and supports the\nFHIR REST API for each resource type defined in the specification."),Object(i.b)("p",null,"However, one thing that neither the HL7 FHIR specification nor the IBM FHIR Server prescribe is the security model for this interface.\nEnter SMART on FHIR."),Object(i.b)("p",null,Object(i.b)("a",Object(n.a)({parentName:"p"},{href:"https://docs.smarthealthit.org"}),"SMART on FHIR")," is a collection of specifications that focus on authentication and authorization on top of HL7 FHIR.\nIn November of 2018, Boston Children’s Hospital and HL7 published the first of these specifications called the ",Object(i.b)("a",Object(n.a)({parentName:"p"},{href:"http://www.hl7.org/fhir/smart-app-launch/"}),"SMART App Launch Framework"),".\nThis specification extends OAuth 2.0 with custom ",Object(i.b)("inlineCode",{parentName:"p"},"scopes")," and the notion of ",Object(i.b)("inlineCode",{parentName:"p"},"launch context"),"."),Object(i.b)("p",null,"SMART ",Object(i.b)("em",{parentName:"p"},"scopes")," are normal OAuth 2.0 scope strings that are used to define an authorization model that is aligned with FHIR resources.\nThe specification defines the scope strings via a pattern like ",Object(i.b)("inlineCode",{parentName:"p"},"<context>/<resourceType>.<permission>"),", where"),Object(i.b)("ul",null,Object(i.b)("li",{parentName:"ul"},Object(i.b)("em",{parentName:"li"},"context")," is ",Object(i.b)("inlineCode",{parentName:"li"},"patient")," or ",Object(i.b)("inlineCode",{parentName:"li"},"user"),";"),Object(i.b)("li",{parentName:"ul"},Object(i.b)("em",{parentName:"li"},"resourceType")," is one of the resource types defined in the HL7 FHIR specification, or ",Object(i.b)("inlineCode",{parentName:"li"},"*")," to indicate all resource types; and"),Object(i.b)("li",{parentName:"ul"},Object(i.b)("em",{parentName:"li"},"permission")," is ",Object(i.b)("inlineCode",{parentName:"li"},"read")," or ",Object(i.b)("inlineCode",{parentName:"li"},"write"),", or ",Object(i.b)("inlineCode",{parentName:"li"},"*")," for read and write.")),Object(i.b)("p",null,"SMART ",Object(i.b)("em",{parentName:"p"},"launch context")," is what allows a user to launch an application in the context of a given user session or patient record.\nSMART App Launch defines two different launch varieties: one for launching apps from an Electronic Health Record (EHR) system and one for launching “standalone” apps."),Object(i.b)("p",null,"In this post, I walk through how to support standalone app launch with the IBM FHIR Server via ",Object(i.b)("a",Object(n.a)({parentName:"p"},{href:"https://www.keycloak.org"}),"Keycloak"),",\nan open source identity and access management solution."),Object(i.b)("h3",null,"Introducing the Alvearie Keycloak Extensions for FHIR"),Object(i.b)("p",null,"Keycloak is wonderfully extensible and easily configurable.\nUsers can automate the creation of SMART ",Object(i.b)("em",{parentName:"p"},"scopes")," through scipting or configuration, or manually create them via the admin console.\nHowever, supporting SMART ",Object(i.b)("em",{parentName:"p"},"launch context")," is a bit more tricky."),Object(i.b)("p",null,"In particular, SMART extends the OAuth 2.0 / OpenID Connect token ",Object(i.b)("em",{parentName:"p"},"response payload")," with a set of ",Object(i.b)("a",Object(n.a)({parentName:"p"},{href:"http://www.hl7.org/fhir/smart-app-launch/scopes-and-launch-context/index.html#launch-context-arrives-with-your-access_token"}),"launch context parameters"),". For example, in the case of the ",Object(i.b)("inlineCode",{parentName:"p"},"patient")," context, a SMART-enabled client application would expect a ",Object(i.b)("inlineCode",{parentName:"p"},"patient")," parameter in the token response payload (alongside the access token)."),Object(i.b)("p",null,"In addition to the above, SMART App Launch also defines a custom ",Object(i.b)("inlineCode",{parentName:"p"},"aud")," parameter that clients must pass on their ",Object(i.b)("a",Object(n.a)({parentName:"p"},{href:"http://www.hl7.org/fhir/smart-app-launch/#step-1-app-asks-for-authorization"}),"authorization")," request (in addition to the fields required by OAuth 2.0 / OpenID Connect). Per the SMART “best practices” document at ",Object(i.b)("a",Object(n.a)({parentName:"p"},{href:"http://docs.smarthealthit.org/authorization/best-practices/#25-access-token-phishing-by-counterfeit-resource-servers"}),"http://docs.smarthealthit.org/authorization/best-practices/#25-access-token-phishing-by-counterfeit-resource-servers"),", authorization servers should validate this field and use this same value for the ",Object(i.b)("inlineCode",{parentName:"p"},"aud")," claim in the granted access token."),Object(i.b)("p",null,"Neither of these OAuth extensions are supported by Keycloak out-of-the-box (nor almost any other commercial Authorization server) and, for these reasons, the IBM team behind the IBM FHIR Server have introduced a set of ",Object(i.b)("a",Object(n.a)({parentName:"p"},{href:"https://github.com/Alvearie/keycloak-extensions-for-fhir"}),"Keycloak extensions")," that fill these gaps."),Object(i.b)("h3",null,"Using the Alvearie Keycloak Extensions for FHIR"),Object(i.b)("p",null,"The Keycloak extensions for FHIR project currently consists of two components:"),Object(i.b)("ul",null,Object(i.b)("li",{parentName:"ul"},Object(i.b)("em",{parentName:"li"},"keycloak-config")," for automating the configuration of a Keycloak realm in support of standalone app launch with patient context"),Object(i.b)("li",{parentName:"ul"},Object(i.b)("em",{parentName:"li"},"keycloak-extensions")," which defines a set of extensions that fill the gaps in Keycloak’s support of SMART App Launch:",Object(i.b)("ul",{parentName:"li"},Object(i.b)("li",{parentName:"ul"},"an AudienceValidator authenticator for validating that the ",Object(i.b)("inlineCode",{parentName:"li"},"aud")," parameter passed to the server matches the desired value"),Object(i.b)("li",{parentName:"ul"},"a UserAttributeMapper mapper for mapping user attributes into custom fields in the token response payload (rather than claims in the issued tokens)")))),Object(i.b)("p",null,"To use the extensions:"),Object(i.b)("ol",null,Object(i.b)("li",{parentName:"ol"},"Build the keycloak-extensions project into a jar file and add it to the ",Object(i.b)("a",Object(n.a)({parentName:"li"},{href:"https://www.keycloak.org/docs/latest/server_development/#packaging-classes-and-deployment-2"}),Object(i.b)("inlineCode",{parentName:"a"},"standalone/deployments"))," directory of your Keycloak installation.",Object(i.b)("ul",{parentName:"li"},Object(i.b)("li",{parentName:"ul"},"Note: moving forward, we hope to publish the built artifacts to Maven Central."))),Object(i.b)("li",{parentName:"ol"},"Configure a realm in your Keycloak installation for SMART App Launch, either manually or via the ",Object(i.b)("inlineCode",{parentName:"li"},"keycloak-config")," project.",Object(i.b)("ul",{parentName:"li"},Object(i.b)("li",{parentName:"ul"},"In addition to defining all the SMART scopes, the ",Object(i.b)("inlineCode",{parentName:"li"},"keycloak-config")," automation will create a default group called ",Object(i.b)("inlineCode",{parentName:"li"},"fhirUser")," (so that all new users will belong to it), and a Group Membership mapper for adding this group to the access token. This group detail is used to map users to a security-role in the IBM FHIR Server (described below).\n",Object(i.b)("img",Object(n.a)({parentName:"li"},{src:"https://user-images.githubusercontent.com/5478798/111356103-68e7c680-865e-11eb-8d0c-1c4315a78634.png",alt:"Group-Membership-mapper"}))),Object(i.b)("li",{parentName:"ul"},"The ",Object(i.b)("inlineCode",{parentName:"li"},"keycloak-config")," automation does ",Object(i.b)("strong",{parentName:"li"},"not")," yet add a mapper for adding the patient parameter to the token response. To support that, add the ",Object(i.b)("inlineCode",{parentName:"li"},"User Attribute (with token response support)")," Mapper to the ",Object(i.b)("inlineCode",{parentName:"li"},"launch/patient")," scope and configure it to map a user attribute (e.g. ",Object(i.b)("inlineCode",{parentName:"li"},"resourceId"),") to the token response.\n",Object(i.b)("img",Object(n.a)({parentName:"li"},{src:"https://user-images.githubusercontent.com/5478798/111354064-4c4a8f00-865c-11eb-9913-e5931e883175.png",alt:"patient-id-mapper"}))))),Object(i.b)("li",{parentName:"ol"},"Manually define a new ",Object(i.b)("em",{parentName:"li"},"Authentication Flow")," that requires Audience Validation before login.\n",Object(i.b)("img",Object(n.a)({parentName:"li"},{src:"https://user-images.githubusercontent.com/5478798/111352797-f75a4900-865a-11eb-9850-302cd3c47973.png",alt:"SMART-Authentication-flow"}))),Object(i.b)("li",{parentName:"ol"},"Add your users and ensure they have a user attribute value that matches the one you mapped in step two.\n",Object(i.b)("img",Object(n.a)({parentName:"li"},{src:"https://user-images.githubusercontent.com/5478798/111354473-bb27e800-865c-11eb-8432-94e0a647beaa.png",alt:"User-attribute"})))),Object(i.b)("h3",null,"Configuring the IBM FHIR Server"),Object(i.b)("p",null,"Now that you have Keycloak configured, its time to hook up the IBM FHIR Server.\nThe IBM FHIR Server supports ",Object(i.b)("a",Object(n.a)({parentName:"p"},{href:"https://openliberty.io/docs/latest/reference/config/server-configuration-overview.html#server-xml"}),"OpenLiberty configDropins")," for configuring server behavior. To configure the server to validate Keycloak-issued tokens, modify ",Object(i.b)("inlineCode",{parentName:"p"},"configDropins/disabled/jwtRP.xml")," for you Keycloak installation and move it to ",Object(i.b)("inlineCode",{parentName:"p"},"configDropins/overrides/"),":"),Object(i.b)("pre",null,Object(i.b)("code",Object(n.a)({parentName:"pre"},{className:"language-xml"}),'<server>\n    \x3c!-- Enable features --\x3e\n    <featureManager>\n        \x3c!-- mpJwt-1.1 is already enabled in the default server.xml, but it doesn\'t hurt to repeat it here --\x3e\n        <feature>mpJwt-1.1</feature>\n    </featureManager>\n\n    \x3c!-- Override the application-bnd binding of the main webapp --\x3e\n    <webApplication contextRoot="fhir-server/api/v4" id="fhir-server-webapp" location="fhir-server.war" name="fhir-server-webapp">\n        <application-bnd id="bind">\n            <security-role id="users" name="FHIRUsers">\n                <group id="usersGroup" access-id="group:https://${env.HOSTNAME}/auth/realms/${env.KEYCLOAK_REALM}/fhirUser"/>\n            </security-role>\n        </application-bnd>\n    </webApplication>\n\n    \x3c!-- This MP JWT configuration validates JWT access tokens and injects them into\n         ResourceScoped beans for inspection. --\x3e\n    <mpJwt id="jwtConsumer"\n           clockSkew="1m"\n           jwksUri="http://keycloak:8080/auth/realms/${env.KEYCLOAK_REALM}/protocol/openid-connect/certs"\n           audiences="https://${env.HOSTNAME}/fhir-server/api/v4"\n           userNameAttribute="sub"\n           groupNameAttribute="group"\n           issuer="https://${env.HOSTNAME}/auth/realms/${env.KEYCLOAK_REALM}"\n           signatureAlgorithm="RS256"\n           authFilterRef="filter"/>\n\n    <authFilter id="filter">\n        <requestUrl urlPattern="/fhir-server" />\n    </authFilter>\n</server>\n')),Object(i.b)("p",null,"Additionally, to enforce the SMART scopes passed, the server must be configured with the ",Object(i.b)("a",Object(n.a)({parentName:"p"},{href:"https://bintray.com/ibm-watson-health/ibm-fhir-server-releases/fhir-smart"}),"fhir-smart")," persistence interceptor (by dropping the jar file into the ",Object(i.b)("inlineCode",{parentName:"p"},"userlib")," directory of your IBM FHIR Server installation)."),Object(i.b)("p",null,"Finaly, to advertise the SMART support and make the auth endpoints discoverable, update the ",Object(i.b)("inlineCode",{parentName:"p"},"security")," section of the ",Object(i.b)("inlineCode",{parentName:"p"},"fhir-server-config.json")," as described in the ",Object(i.b)("a",Object(n.a)({parentName:"p"},{href:"https://ibm.github.io/FHIR/guides/FHIRServerUsersGuide#533-advertise-the-oauth-endpoints-via-fhir-server-config"}),"IBM FHIR Server User’s Guide"),":"),Object(i.b)("pre",null,Object(i.b)("code",Object(n.a)({parentName:"pre"},{className:"language-json"}),'...\n    "security": {\n      "cors": true,\n      "basic": {\n        "enabled": false\n      },\n      "certificates": {\n        "enabled": false,\n        "authFilter": {\n          "enabled": false\n        }\n      },\n      "oauth": {\n        "enabled": true,\n        "regUrl": "https://<host>/auth/realms/${KEYCLOAK_REALM>}/clients-registrations/openid-connect",\n        "authUrl": "https://<host>/auth/realms/${KEYCLOAK_REALM}/protocol/openid-connect/auth",\n        "tokenUrl": "https://<host>/auth/realms/${KEYCLOAK_REALM}/protocol/openid-connect/token",\n        "smart": {\n          "enabled": true,\n          "scopes": ["openid", "profile", "fhirUser", "launch/patient", "offline_access",\n            "patient/*.read",\n            "patient/AllergyIntolerance.read",\n            "patient/CarePlan.read",\n            "patient/CareTeam.read",\n            "patient/Condition.read",\n            "patient/Device.read",\n            "patient/DiagnosticReport.read",\n            "patient/DocumentReference.read",\n            "patient/Encounter.read",\n            "patient/ExplanationOfBenefit.read",\n            "patient/Goal.read",\n            "patient/Immunization.read",\n            "patient/Location.read",\n            "patient/Medication.read",\n            "patient/MedicationRequest.read",\n            "patient/Observation.read",\n            "patient/Organization.read",\n            "patient/Patient.read",\n            "patient/Practitioner.read",\n            "patient/PractitionerRole.read",\n            "patient/Procedure.read",\n            "patient/Provenance.read",\n            "patient/RelatedPerson.read"\n          ],\n          "capabilities": [\n            "sso-openid-connect",\n            "launch-standalone",\n            "client-public",\n            "client-confidential-symmetric",\n            "permission-offline",\n            "context-standalone-patient",\n            "permission-patient"\n          ]\n        }\n      }\n    },\n...\n')),Object(i.b)("h3",null,"Conclusion"),Object(i.b)("p",null,"In this post, we walked through the OAuth 2.0 extensions defined by SMART App Launch, how to support those extensions via the Alvearie Keycloak Extensions for FHIR, and how to use this project together with the IBM FHIR Server to support SMART App Launch."),Object(i.b)("p",null,"SMART App Launch is just one of the specifications defined by SMART-on-FHIR and the authentication and authorization challenges in healthcare are immense. We’d love to hear from members of the FHIR community, the SMART-on-FHIR community, and the Keycloak community on the approach outlined here and where you’d like to see us go next. Please connect with us on ",Object(i.b)("a",Object(n.a)({parentName:"p"},{href:"https://chat.fhir.org/#narrow/stream/179170-smart/topic/Keycloak.20for.20SMART.20authz"}),"chat.fhir.org"),", the ",Object(i.b)("a",Object(n.a)({parentName:"p"},{href:"https://github.com/Alvearie/keycloak-extensions-for-fhir"}),"Keycloak Extensions for FHIR project"),", or the ",Object(i.b)("a",Object(n.a)({parentName:"p"},{href:"https://github.com/ibm/fhir"}),"IBM FHIR Server project")," about how we can take this to the next level."),Object(i.b)("hr",null),Object(i.b)("p",null,"SMART is a trademark of The Children’s Medical Center Corporation.\nFHIR® is the registered trademark of HL7 and is used with the permission of HL7. Use of the FHIR trademark does not constitute endorsement of this product by HL7.\nIBM and the IBM logo are trademarks of International Business Machines Corporation, registered in many jurisdictions worldwide.\nOther product and service names might be trademarks of IBM or other companies. A current list of IBM trademarks is available on ",Object(i.b)("a",Object(n.a)({parentName:"p"},{href:"https://ibm.com/trademark"}),"https://ibm.com/trademark"),"."))}p.isMDXComponent=!0},I8xM:function(e,t,a){e.exports={bxTextTruncateEnd:"PageTabs-module--bx--text-truncate--end--3BhKg",bxTextTruncateFront:"PageTabs-module--bx--text-truncate--front--3k3x1",tabsContainer:"PageTabs-module--tabs-container--3ku4u",list:"PageTabs-module--list--Zs80A",listItem:"PageTabs-module--list-item--UVTiB",link:"PageTabs-module--link--TAIyd",selectedItem:"PageTabs-module--selected-item--2Kv-J"}},QH2O:function(e,t,a){e.exports={bxTextTruncateEnd:"PageHeader-module--bx--text-truncate--end--2SjuB",bxTextTruncateFront:"PageHeader-module--bx--text-truncate--front--3jsoz",pageHeader:"PageHeader-module--page-header--365gs",darkMode:"PageHeader-module--dark-mode--1Y-8D",withTabs:"PageHeader-module--with-tabs--2y_Tu",text:"PageHeader-module--text--3r6mD"}}}]);
  //# sourceMappingURL=component---src-pages-blog-smart-keycloak-index-mdx-d7d1527dfc5d6fb6214f.js.map