Get hands-on experience with 20+ free Google Cloud products and $300 in free credit for new customers.

Constrain access to a fhir store resources through a Proxy (smart on fhir?)

I have a GCP Healthcare API deployed in GCP. I have an API Product & proxy returning data ok. The IAM service account in the Apigee project has firestore reader permissions in the fhir store project and to access the fhir store my proxy is reading the fhir store like this :

 

  <HTTPTargetConnection>
    <URL>https://SET_URL_FROM_CONFIG</URL>
    <Authentication>
      <GoogleAccessToken>
        <Scopes>
          <Scope>https://www.googleapis.com/auth/cloud-platform</Scope>
        </Scopes>
      </GoogleAccessToken>
    </Authentication>
  </HTTPTargetConnection>

 

The above works just fine.

I'm looking to create an API Product that constrains access to a subset of fhir resources. e.g. Organization, Location, Practitioner

What are my options?

What I've tried:

1. Using Operations --> No good. I can constrain the proxy by Operations e.g. /Practitioner/** but this is easily circumvented with a fhir reverse include query like this :
proxyname/Practitioner?_id=<unique_fhirstore_id>&_revinclude=Encounter:practitioner

2. Was hoping using scopes like this below would work? docs ref
See the various attempts commented out..

 

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<AssignMessage continueOnError="false" enabled="true" name="AssignMessage-AddScopesHeader">
    <DisplayName>AssignMessage-AddScopesHeader</DisplayName>
    <Properties/>
    <Add>
        <!-- https://cloud.google.com/healthcare-api/docs/smart-on-fhir#set-and -->
        <!-- This is a scopes test. Will this restrict the FHIR server as described ?? -->
        <Headers>
            <!-- <Header name="X-Authorization-Scope">user/Practitioner.read user/PractitionerRole.read user/Organization.read user/Location.read</Header> -->
            <!-- <Header name="X-Authorization-Scope">user/Practitioner.read</Header> -->
            <!-- <Header name="X-Authorization-Scope">user/*.rs</Header> -->
            <Header name="X-Authorization-Scope">user/*.read</Header>
        </Headers>
    </Add>
    <IgnoreUnresolvedVariables>true</IgnoreUnresolvedVariables>
    <AssignTo createNew="false" transport="http" type="request"/>
</AssignMessage>

 

It's not behaving at the moment. Still trying it. Thought I'd ask if in case I was heading down a rabbit hole!

Thanks













Solved Solved
0 7 1,110
3 ACCEPTED SOLUTIONS

What you're doing makes sense to me.

Injecting headers like the X-Authorization-Scope to communicate to Cloud Healthcare API what access control is necessary, is an exemplary use of Apigee in front of FHIRStore.

If I were you, I would make a couple changes:

  1. use Set instead of Add. Set overwrites any existing header, which is what you want here. Add will ... add to any existing header, or set the header if it is not already present. By using Set, you insure that the header holds only the value your proxy intends to be there. You don't want the client to be able to send in a X-Authorization-Scope header, and then have Apigee simply add to it. This would be a security vulnerability, obviously.
  2. Omit the

     <AssignTo createNew="false" transport="http" type="request"/>

    It does nothing. You DO have to make sure you attach that AssignMessage policy into the Request flow, in order for it to be received by the Healthcare API.

View solution in original post

I was trying to avoid adding a call to an Authorisation server (to turn a JWT with scopes into an access token as you've done here in github) between Apigee and the Healthcare API.

YES. And the Authentication element.... this one:

 

     <HTTPTargetConnection>
        <URL>https://SET_URL_FROM_CONFIG</URL>
        <Authentication>
            <GoogleIDToken>
                <Audience useTargetUrl="true"/>
            </GoogleIDToken>
        </Authentication>
        ...

 

Does that for you.

OR DOES IT?

You specifically wrote

...turn a JWT with scopes into an access token between Apigee and the Healthcare API.

I want to call your attention to the options under the Authentication element. With the child element, you can tell Apigee to either send an ID TOKEN or an ACCESS TOKEN in the Authorization header. With the configuration shown above, using GoogleIDToken, it sends an ID TOKEN. I think what you want is an ACCESS TOKEN, so you must use GoogleAccessToken in the configuration. Like this:

 

     <HTTPTargetConnection>
        <URL>https://SET_URL_FROM_CONFIG</URL>
        <Authentication>
          <GoogleAccessToken>
            <Scopes>
              <Scope>https://www.googleapis.com/auth/cloud-platform</Scope>
            </Scopes>
          </GoogleAccessToken>
        </Authentication>
        ...

 

View solution in original post

Wow! It works! Was expecting a bit of a mammoth struggle (my bad!). It's super helpful to get a clear answer here when you are struggling a bit. Thanks @dchiesa1. To Summarize what I now have in place:

The Target endpoint:

 

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<TargetEndpoint name="default">
  <PreFlow>
    <Request>
      <Step>
        <Name>AssignMessage-AddScopesHeader</Name>
      </Step>
    </Request>
    <Response>
      <!-- This shared flow replaces any occurrences of the HTTPTargetConnection/URL with the proxy base URL  -->
      <Step>
        <Name>FlowCallout-RedirectBackendUrls</Name>
      </Step>
    </Response>
  </PreFlow>
  <PostFlow>
    <Request>
      <Step>
        <Name>FlowCallout-SetTargetUrlFromConfig</Name>
      </Step>
    </Request>
  </PostFlow>
  <HTTPTargetConnection>
    <URL>https://SET_URL_FROM_CONFIG</URL>
    <Authentication>
      <GoogleAccessToken>
        <Scopes>
          <Scope>https://www.googleapis.com/auth/cloud-platform</Scope>
        </Scopes>
      </GoogleAccessToken>
    </Authentication>
  </HTTPTargetConnection>
</TargetEndpoint>

 

 The fixed scopes header limiting the fhir server using scopes:

 

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<AssignMessage continueOnError="false" enabled="true" name="AssignMessage-AddScopesHeader">
  <DisplayName>AssignMessage-AddScopesHeader</DisplayName>
  <Properties/>
  <Set>
    <!-- https://cloud.google.com/healthcare-api/docs/smart-on-fhir#set-and -->
    <!-- This is a scopes test. Will this restrict the FHIR server as described ?? -->
    <Headers>
      <Header name="X-Authorization-Scope">user/Practitioner.read user/Organization.read user/PractitionerRole.read user/Location.read</Header>
    </Headers>
  </Set>
  <IgnoreUnresolvedVariables>true</IgnoreUnresolvedVariables>
</AssignMessage>

 

Result:

- I can read the above 4 fhir resources fine. <proxy>/Organization ... etc
- When I now query a patient fhir resource that is on the Cloud Healthcare API the server is responding with 403 forbidden and the message below:

 

{
    "issue": [
        {
            "code": "security",
            "details": {
                "text": "permission_denied"
            },
            "diagnostics": "SMART access denied or the resource being accessed does not exist",
            "severity": "error"
        }
    ],
    "resourceType": "OperationOutcome"

 

This proves the SMART on fhir scopes are having the desired affect. I may look at how I can tidy up that message but it's nice and clear that the backend is doing what we wanted. 

I would like to be able to also pull this off with a JWT signed with the service account key which I think we will need in the future (for flexibly injecting scopes) but I'll post a separate post about how that goes (I'll have a few more questions no doubt). I'm assuming this repo is relevant for JWT generation for the Clould Healthcare API.

Marking this as solved. Thanks again.

 


View solution in original post

7 REPLIES 7