LinuxForHealth / FHIR

The LinuxForHealth FHIR® Server and related projects
https://linuxforhealth.github.io/FHIR
Apache License 2.0
330 stars 157 forks source link

SMART EHR APP Launch JWT Authentication error #4163

Open SturmCamper opened 1 year ago

SturmCamper commented 1 year ago

Describe the bug I was currently tinkering with SMART on Fhir and tried to impliment my own basic EHR. Every thing works fine until I try to launch an APP within the EHR context. The problem should be the authetication, since whenever I'm going through the launch flow the Following messages appear inside the LinuxForHealth container.

hir-fhir-server-1     | [1/16/23, 17:33:07:479 UTC] 00000034 TAIJwtUtils   E   CWWKS5524E: The MicroProfile JWT feature encountered an error while creating a JWT by using the [jwtConsumer] configuration and the token included in the request. CWWKS6031E: The JSON Web Token (JWT) consumer [jwtConsumer] cannot process the token string. CWWKS6063E: The token in the request is not in JSON Web Signature (JWS) format, but the [jwtConsumer] JWT consumer only accepts tokens that are in JWS format. 
fhir-fhir-server-1     | [1/16/23, 17:33:07:480 UTC] 00000034 MicroProfileJ E   CWWKS5523E: The MicroProfile JWT feature cannot authenticate the request because the token that is included in the request cannot be validated. CWWKS5524E: The MicroProfile JWT feature encountered an error while creating a JWT by using the [jwtConsumer] configuration and the token included in the request. CWWKS6031E: The JSON Web Token (JWT) consumer [jwtConsumer] cannot process the token string. CWWKS6063E: The token in the request is not in JSON Web Signature (JWS) format, but the [jwtConsumer] JWT consumer only accepts tokens that are in JWS format.

I'm using the fhir server in conjunction with the Keycloak extension and maby there is some Config issue, but at this time I can't seem to find any. If i genereate a Toke with postman and try to access one resource the Problem does not occour. Maby it has something to do with Fhir-Client the Apps implement?

Environment The Enviroment should be the latest. To enforce the SMART-interceptor im Using the the smar-fhir.jar version 5.1.1.

For the setup I'm using docker compose

version: '3.7'
services:
  fhir-server:
    image: ghcr.io/linuxforhealth/fhir-server
    hostname: fhir
    volumes:
      - type: bind
        source: $PWD/config
        target: /opt/ol/wlp/usr/servers/defaultServer/config
        read_only: true
      - type: bind
        source: $PWD/configDropins/overrides
        target: /opt/ol/wlp/usr/servers/defaultServer/configDropins/overrides
        read_only: true
      - type: bind
        source: $PWD/userlib
        target: /opt/ol/wlp/usr/servers/defaultServer/userlib
        read_only: true
      - type: bind
        source: $PWD/derby
        target: /output/derby
    environment:
      - BOOTSTRAP_DB=false
      - TRACE_SPEC=com.ibm.fhir.*=info
      - TRACE_FILE=stdout
    ports:
      - 9080:9080
      - 9443:9443
    healthcheck:
      start_period: 32s
      interval: 120s
      timeout: 5s
      retries: 3
      test: curl -k -u 'fhiruser:change-password' 'https://localhost:9443/fhir-server/api/v4/metadata'
    networks:
      - backend
  keycloak:
    image: alvearie/smart-keycloak
    hostname: keycloak
    environment:
      - KEYCLOAK_USER=admin
      - KEYCLOAK_PASSWORD=admin
    ports:
      - 8080:8080
      - 8443:8443
    healthcheck:
      start_period: 32s
      interval: 10s
      timeout: 5s
      retries: 3
      # https://docs.docker.com/compose/compose-file/#variable-substitution
      test: curl -k 'https://localhost:8443/auth'
    networks:
      - backend
  keycloak-setup:
    image: alvearie/keycloak-config
    hostname: keycloak-config
    environment:
      - KEYCLOAK_BASE_URL=http://keycloak:8080/auth
    depends_on:
      keycloak: 
        condition: service_healthy
    networks:
      - backend
networks:
  backend:
    driver: bridge

The server Config

{
    "comment": "FHIR Server configuration",
    "fhirServer": {
        "core": {
            "tenantIdHeaderName": "X-FHIR-TENANT-ID",
            "datastoreIdHeaderName": "X-FHIR-DSID",
            "originalRequestUriHeaderName": "X-FHIR-FORWARDED-URL",
            "checkReferenceTypes": true,
            "conditionalDeleteMaxNumber": 10,
            "comment": "The CI changes this to true, and the false is intentional as we recommend for performance reasons.",
            "serverRegistryResourceProviderEnabled": false,
            "disabledOperations": ""
        },
        "security": {
            "cors": false,
            "basic": {
                "enabled": false
            },
            "certificates": {
                "enabled": false
            },
            "oauth": {
                "enabled": true,
                "regUrl": "https:localhost:8443/auth/realms/test/protocol/openid-connect/certs",
                "authUrl": "https://localhost:8443/auth/realms/test/protocol/openid-connect/auth",
                "tokenUrl": "https://localhost:8443/auth/realms/test/protocol/openid-connect/token",
                "smart": {
                    "enabled": true,
                   "scopes": ["openid", "profile", "fhirUser", "launch/patient", "offline_access",
            "patient/*.read",
            "patient/AllergyIntolerance.read",
            "patient/CarePlan.read",
            "patient/CareTeam.read",
            "patient/Condition.read",
            "patient/Device.read",
            "patient/DiagnosticReport.read",
            "patient/DocumentReference.read",
            "patient/Encounter.read",
            "patient/ExplanationOfBenefit.read",
            "patient/Goal.read",
            "patient/Immunization.read",
            "patient/Location.read",
            "patient/Medication.read",
            "patient/MedicationRequest.read",
            "patient/Observation.read",
            "patient/Organization.read",
            "patient/Patient.read",
            "patient/Practitioner.read",
            "patient/PractitionerRole.read",
            "patient/Procedure.read",
            "patient/Provenance.read",
            "patient/RelatedPerson.read"
          ],
          "capabilities": [
            "sso-openid-connect",
            "launch-standalone",
            "client-public",
            "client-confidential-symmetric",
            "permission-offline",
            "context-standalone-patient",
            "permission-patient"
          ]
                }
            }
        },
        "audit": {
            "serviceClassName" : "org.linuxforhealth.fhir.audit.impl.NopService",
            "serviceProperties" : {
            }
        },
        "persistence": {
            "factoryClassname": "org.linuxforhealth.fhir.persistence.jdbc.FHIRPersistenceJDBCFactory",
            "common": {
                "comment": "Configuration properties common to all persistence layer implementations",
                "updateCreateEnabled": true
            },
            "jdbc": {
                "comment": "Configuration properties for the JDBC persistence implementation",
                "enableCodeSystemsCache": true,
                "enableParameterNamesCache": true,
                "enableResourceTypesCache": true
            },
            "datasources": {
                "default": {
                    "type": "postgresql",
                    "currentSchema": "fhirdata",
                    "searchOptimizerOptions": {
                        "from_collapse_limit": 12,
                        "join_collapse_limit": 12
                    }
                }
            }
        },
        "bulkdata": {
            "enabled": false
        },
        "operations": {
            "erase": {
                "enabled": true,
                "allowedRoles": [
                    "FHIROperationAdmin",
                    "FHIRUsers"
                ]
            }
        }
    }
}

The JWT config

<server description="fhir-server">
    <featureManager>
        <!-- mpJwt-1.2 is already enabled in the default server.xml, but it doesn't hurt to repeat it here -->
        <feature>mpJwt-1.2</feature>
    </featureManager>

    <!-- Override the application-bnd binding of the main webapp -->
    <webApplication contextRoot="fhir-server/api/v4" id="fhir-server-webapp" location="fhir-server.war" name="fhir-server-webapp">
        <application-bnd id="bind">
            <security-role id="users" name="FHIRUsers">
                <group id="usersGroup" access-id="group:https://localhost:8443/auth/realms/test/fhirUser%22/%3E
            </security-role>
        </application-bnd>
    </webApplication>

    <!-- The MP JWT configuration that injects the caller's JWT into a
         ResourceScoped bean for inspection. -->
    <mpJwt id="jwtConsumer"
           jwksUri="http://keycloak:8080/auth/realms/test/protocol/openid-connect/certs"
           audiences="http://fhir-server:9080/fhir-server/api/v4"
           userNameAttribute="sub"
           groupNameAttribute="group"
           issuer="https://localhost:8443/auth/realms/test"
           authFilterRef="filter"/>

    <authFilter id="filter">
        <requestUrl urlPattern="/fhir-server" />
    </authFilter>
</server>

To Reproduce For a test I used this App https://bilirubin-risk-chart.logicahealth.org/launch.html?

I created a fitting Keycloak client, but as I said I'm not sure if I configured everything correctly. As a how to config I used this guide https://alvearie.io/blog/smart-keycloak/

Thanks in advance for every help. And sry if this might seem self explanatory, but I can't find any ressources to help my self.

lmsurpre commented 1 year ago

Hi @SturmCamper, if you can obtain an access token and invoke the service using that token, then I think it indicates things are working on our end. Unfortunately, I'm not familiar with https://bilirubin-risk-chart.logicahealth.org/launch.html?](https://bilirubin-risk-chart.logicahealth.org/launch.html but the error message you provided makes it sound like the server is getting a request that doesn't actually have a JWT bearer token. Use of JWTs isn't required by the spec, but if the client is interacting with our smart auth server (keycloak) then it should be getting one. Are you able to intercept that request to check what the Authentication header looks like in the request?

P.S. Not sure if its related to your issues or not, but we've mostly worked with "standalone app launch" scenarios, not EHR launch scenarios.

hoffmka commented 2 months ago

Hi @SturmCamper , you wrote:

"To enforce the SMART-interceptor im using the smart-fhir.jar version 5.1.1."

I'm wondering where I can get the jar file from. In the FHIR server documentation is written:

To enforce authorization policy on the server, drop the fhir-smart module into the server’s userlib directory.

I assume that your jar file is the module that should be dropped in the userlib directory. But where can I get the jar file from? Or do I need to create it from the fhir-smart directory? Sorry for my question, but I'm very new in Java development.

P.S. I have set up a demo stack with Keycloak and am able to get a valid access token (aud, scopes, referenceID are parts of the token). But when I try to retrieve the patient's resource, I get a Forbidden response (403). So I think the fhir-smart module is not enabled in my test server. Is there a way to check if the fhir-smart module is enabled or not? Or is there a way to determine the reasons why the request ends with a 403 forbidden response?

Many thanks in advance. Katja

UPDATE: I think I have found the repository with the available jar files --> https://repo1.maven.org/maven2/org/linuxforhealth/fhir/fhir-smart/5.1.1/ I have copied fhir-smart-5.1.1.jar to the userlib directory and restart the docker container. But unfortunately I always get a 403 response when I try to access the patient resource. Is there a server log with detailed information as to what the reason is?