Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How does one set up for backend auth? #69

Open
mzbik opened this issue May 2, 2022 · 2 comments
Open

How does one set up for backend auth? #69

mzbik opened this issue May 2, 2022 · 2 comments
Labels
question Further information is requested

Comments

@mzbik
Copy link

mzbik commented May 2, 2022

See [https://hl7.org/fhir/smart-app-launch/backend-services.html]. I don't see where I can do the client-confidential-asymmetric registration.

@lmsurpre lmsurpre added the question Further information is requested label May 3, 2022
@lmsurpre
Copy link
Collaborator

lmsurpre commented May 3, 2022

It could definitely use some documentation, but here's a keycloak-config.json snippet that shows two clients.

  • inferno would be a typical SMART App Launch config
  • infernoBulk would be a typical SMART Backend Services config
            "clients": {
              "inferno": {
                "consentRequired": true,
                "publicClient": true,
                "redirectURIs": ["http://localhost:4567/inferno/*"],
                "standardFlowEnabled": true,
                "serviceAccountsEnabled": false,
                "clientAuthenticatorType": "client-secret",
                "defaultClientScopes": ["launch/patient"],
                "optionalClientScopes":["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/MedicationDispense.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"]
              },
              "infernoBulk": {
                "consentRequired": false,
                "publicClient": false,
                "redirectURIs": null,
                "standardFlowEnabled": false,
                "serviceAccountsEnabled": true,
                "clientAuthenticatorType": "client-jwt",
                "defaultClientScopes": [],
                "optionalClientScopes":["system/*.read","system/AllergyIntolerance.read","system/CarePlan.read","system/CareTeam.read","system/Condition.read","system/Device.read","system/DiagnosticReport.read","system/DocumentReference.read","system/Encounter.read","system/ExplanationOfBenefit.read","system/Goal.read","system/Immunization.read","system/Location.read","system/Medication.read","system/MedicationDispense.read","system/MedicationRequest.read","system/Observation.read","system/Organization.read","system/Patient.read","system/Practitioner.read","system/PractitionerRole.read","system/Procedure.read","system/Provenance.read","system/RelatedPerson.read"]
              }
            },

keycloak-config does most of the work in setting up the confidential client with JWT auth. what it doesn't do is:

  1. configure the JWKS url to use to validate the infernoBulk client requests
  2. configure the serviceAccount with group membership (needed for working with IBM FHIR Server) - Support associating a serviceAccount user with a particular group #33
  3. validate the jku claim of the incoming requests - Investigate options for authenticating jku claims on a client jwt #34

Numbers 1 and 2 can be performed manually from the keycloak admin console.
Number 1 is complicated by the fact that keycloak requires the 'use' field in JWKs whereas the sample ones used by default in many backend services tests use key_ops instead: (https://github.com/smart-on-fhir/fhir-bulk-data-docs/blob/master/sample-jwks/ES384.public.json
Number 3 will require some kind of extension (but IMHO shouldn't really be needed).

In the past, I've also played with Keycloak's dynamic app registration support. It worked just fine, but that was before I was looking into confidential clients that use serviceAccounts and authenticate via JWT.

@kidus-tiliksew
Copy link

@lmsurpre Not sure what I'm doing wrong but I've tried the following steps according to the docs but I am still unable to authenticate a backend service with JWT token

  1. Set jwtRS.xml configuration according to the documentation
<server description="fhir-server">
    <featureManager>
        <!-- mpJwt-1.1 is already enabled in the default server.xml, but it doesn't hurt to repeat it here -->
        <feature>mpJwt-1.1</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:http://localhost:8080/auth/realms/test/fhirUser"/>
            </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://localhost:8080/auth/realms/test/protocol/openid-connect/certs"
           issuer="http://localhost:8080/auth/realms/test"
           audiences="https://localhost:9443/fhir-server/api/v4"
           userNameAttribute="sub"
           groupNameAttribute="group"
           authFilterRef="filter"/>

    <authFilter id="filter">
        <requestUrl urlPattern="/fhir-server" />
        <requestUrl matchType="notContain" urlPattern="/fhir-server/api/v4/metadata" />
        <requestUrl matchType="notContain" urlPattern="/fhir-server/api/v4/.well-known/smart-configuration" />
    </authFilter>
</server>
  1. Set infernoBulk configuration
 {
      "id": "05daf878-2ab7-41f9-8516-d54147d06b71",
      "clientId": "infernoBulk",
      "rootUrl": "https://localhost:9443/fhir-server/api/v4",
      "adminUrl": "",
      "surrogateAuthRequired": false,
      "enabled": true,
      "alwaysDisplayInConsole": false,
      "clientAuthenticatorType": "client-secret",
      "secret": "**********",
      "redirectUris": [],
      "webOrigins": [],
      "notBefore": 0,
      "bearerOnly": false,
      "consentRequired": false,
      "standardFlowEnabled": false,
      "implicitFlowEnabled": false,
      "directAccessGrantsEnabled": true,
      "serviceAccountsEnabled": true,
      "publicClient": false,
      "frontchannelLogout": false,
      "protocol": "openid-connect",
      "attributes": {
        "access.token.lifespan": "600",
        "saml.force.post.binding": "false",
        "saml.multivalued.roles": "false",
        "frontchannel.logout.session.required": "false",
        "oauth2.device.authorization.grant.enabled": "false",
        "use.jwks.url": "false",
        "backchannel.logout.revoke.offline.tokens": "false",
        "saml.server.signature.keyinfo.ext": "false",
        "use.refresh.tokens": "true",
        "oidc.ciba.grant.enabled": "false",
        "use.jwks.string": "false",
        "backchannel.logout.session.required": "false",
        "client_credentials.use_refresh_token": "false",
        "client.offline.session.idle.timeout": "600",
        "require.pushed.authorization.requests": "false",
        "saml.client.signature": "false",
        "client.offline.session.max.lifespan": "600",
        "client.session.max.lifespan": "600",
        "saml.allow.ecp.flow": "false",
        "client.session.idle.timeout": "600",
        "id.token.as.detached.signature": "false",
        "saml.assertion.signature": "false",
        "jwks.string": "",
        "client.secret.creation.time": "1678451030",
        "saml.encrypt": "false",
        "saml.server.signature": "false",
        "exclude.session.state.from.auth.response": "false",
        "saml.artifact.binding": "false",
        "saml_force_name_id_format": "false",
        "acr.loa.map": "{}",
        "tls.client.certificate.bound.access.tokens": "false",
        "saml.authnstatement": "false",
        "display.on.consent.screen": "false",
        "token.response.type.bearer.lower-case": "false",
        "saml.onetimeuse.condition": "false"
      },
      "authenticationFlowBindingOverrides": {},
      "fullScopeAllowed": true,
      "nodeReRegistrationTimeout": -1,
      "protocolMappers": [
        {
          "id": "c2ee8552-9d18-4bab-8e9f-0f8d538d84eb",
          "name": "Client Host",
          "protocol": "openid-connect",
          "protocolMapper": "oidc-usersessionmodel-note-mapper",
          "consentRequired": false,
          "config": {
            "user.session.note": "clientHost",
            "id.token.claim": "true",
            "access.token.claim": "true",
            "claim.name": "clientHost",
            "jsonType.label": "String"
          }
        },
        {
          "id": "2a2fd25f-1152-49cf-ad4e-1a742092c3fa",
          "name": "Client IP Address",
          "protocol": "openid-connect",
          "protocolMapper": "oidc-usersessionmodel-note-mapper",
          "consentRequired": false,
          "config": {
            "user.session.note": "clientAddress",
            "id.token.claim": "true",
            "access.token.claim": "true",
            "claim.name": "clientAddress",
            "jsonType.label": "String"
          }
        },
        {
          "id": "5b4026f4-c3d7-46ec-99d4-d979495224bc",
          "name": "Client ID",
          "protocol": "openid-connect",
          "protocolMapper": "oidc-usersessionmodel-note-mapper",
          "consentRequired": false,
          "config": {
            "user.session.note": "clientId",
            "id.token.claim": "true",
            "access.token.claim": "true",
            "claim.name": "clientId",
            "jsonType.label": "String"
          }
        }
      ],
      "defaultClientScopes": [
        "patient/Medication.read",
        "patient/CareTeam.read",
        "patient/Immunization.read",
        "fhirUser",
        "patient/MedicationDispense.read",
        "system/Condition.*",
        "acr",
        "system/CarePlan.*",
        "patient/Condition.read",
        "user/PractitionerRole.read",
        "patient/CarePlan.read",
        "microprofile-jwt",
        "system/MedicationRequest.*",
        "patient/Practitioner.read",
        "launch/patient",
        "test",
        "system/Patient.*",
        "system/Provenance.*",
        "profile",
        "user/Organization.read",
        "patient/RelatedPerson.read",
        "patient/MedicationRequest.read",
        "system/CareTeam.*",
        "system/Immunization.*",
        "patient/Device.read",
        "patient/Patient.read",
        "phone",
        "patient/*.read",
        "system/*.*",
        "user/Device.read",
        "system/AllergyIntolerance.*",
        "patient/PractitionerRole.read",
        "online_access",
        "roles",
        "user/Practitioner.read",
        "patient/DocumentReference.read",
        "patient/Procedure.read",
        "patient/Provenance.read",
        "system/Device.*",
        "system/Encounter.*",
        "system/Medication.*",
        "offline_access",
        "system/*.read",
        "email",
        "system/Goal.*",
        "address",
        "patient/Goal.read",
        "patient/AllergyIntolerance.read",
        "system/Organization.*",
        "patient/ExplanationOfBenefit.read",
        "user/*.read",
        "system/Observation.*",
        "system/Location.*",
        "system/ExplanationOfBenefit.*",
        "system/DocumentReference.*",
        "system/DiagnosticReport.*",
        "web-origins",
        "patient/DiagnosticReport.read",
        "system/RelatedPerson.*",
        "patient/Location.read",
        "system/Procedure.*",
        "system/Practitioner.*",
        "patient/Observation.read",
        "system/PractitionerRole.*",
        "patient/Organization.read",
        "patient/Encounter.read"
      ],
      "optionalClientScopes": [
        "system/Goal.read",
        "system/Practitioner.read",
        "system/Device.read",
        "system/Condition.read",
        "system/CarePlan.read",
        "system/MedicationDispense.read",
        "system/CareTeam.read",
        "system/Immunization.read",
        "system/MedicationRequest.read",
        "system/Provenance.read",
        "system/Procedure.read",
        "system/Location.read",
        "system/DiagnosticReport.read",
        "system/AllergyIntolerance.read",
        "system/PractitionerRole.read",
        "system/Patient.read",
        "system/Organization.read",
        "system/Medication.read",
        "system/RelatedPerson.read",
        "system/DocumentReference.read",
        "system/ExplanationOfBenefit.read",
        "system/Observation.read",
        "system/Encounter.read"
      ]
    },
  1. Request access token from Keycloak
curl 'http://localhost:8080/auth/realms/test/protocol/openid-connect/token' \
  -H 'accept: application/json' \
  -H 'content-type: application/x-www-form-urlencoded' \
  --data-raw 'client_id=infernoBulk&client_secret=KlsXqU5EffxehmEdjZBfCHsxIH51ZStf&grant_type=client_credentials'
  1. Make request to FHIR server using access token
curl 'https://localhost:9443/fhir-server/api/v4/Patients' \
  -H 'accept: application/json' \
  -H 'authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuZWVkX3BhdGllbnRfYmFubmVyIjp0cnVlLCJzbWFydF9zdHlsZV91cmwiOiJodHRwczovL3NtYXJ0LmFyZ28ucnVuLy9zbWFydC1zdHlsZS5qc29uIiwicGF0aWVudCI6Ijg3YTMzOWQwLThjYWUtNDE4ZS04OWM3LTg2NTFlNmFhYjNjNiIsInRva2VuX3R5cGUiOiJiZWFyZXIiLCJzY29wZSI6ImxhdW5jaC9wYXRpZW50IHBhdGllbnQvT2JzZXJ2YXRpb24ucnMgcGF0aWVudC9QYXRpZW50LnJzIiwiY2xpZW50X2lkIjoiZGVtb19hcHBfd2hhdGV2ZXIiLCJleHBpcmVzX2luIjozNjAwLCJpYXQiOjE2MzM1MzIwMTQsImV4cCI6MTYzMzUzNTYxNH0.PzNw23IZGtBfgpBtbIczthV2hGwanG_eyvthVS8mrG4'

I get 401 authorized error

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

3 participants