How to Call Google (Healthcare) API from Apigee Proxy with JWT as Bearer Access Token

Hello my fellow Apigeeans,

All of the FHIRFLY proxies have to call the Google Healthcare API to get data. To that end, the Google Healthcare API requires an Bearer access_token to be placed in the Authentication header. I have experimented with various method of obtaining said token. That said, I was most intrigued by this method, using a JWT signed with a X509 private key obtained for the Google Healthcare API service account as the Bearer token itself.

I feel like the policy is almost there, but I am getting an error back from the google:

{
 "error": {
 "code": 401,
 "message": "Request had invalid authentication credentials. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.",
 "status": "UNAUTHENTICATED"
 }
}

My JWT generator policy looks like this: (Note I have changed the audience to https://healthcare.googleapis.com/ with no luck)

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<GenerateJWT name="Generate-JWT-SelfSigned">
    <Algorithm>RS256</Algorithm>
    <PrivateKey>
        <Value ref="private.private_key"/>
    </PrivateKey>
    <Issuer>service-myproject@myproject.iam.gserviceaccount.com</Issuer>
    <Subject>service-myproject@myproject.iam.gserviceaccount.com</Subject>
    <Audience>https://accounts.google.com/</Audience>
    <ExpiresIn>240s</ExpiresIn>
    <OutputVariable>self_signed_jwt</OutputVariable>
</GenerateJWT>

The private key was issued to the Service account specified in the issuer and subject through GCloud AIM. I have setup other service account keys (for Apigee Dupal portal for instance)

I add the self_signed_jwt to the Authorization header in an Assign Message policy like so:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<AssignMessage continueOnError="false" enabled="true" name="AM-GOOGLE_TOKEN">
    <DisplayName>AM-GOOGLE_TOKEN</DisplayName>
    <Properties/>
    <Set>
        <Headers>
            <Header name="Authorization">Bearer {self_signed_jwt}</Header>
        </Headers>
    </Set>
    <IgnoreUnresolvedVariables>true</IgnoreUnresolvedVariables>
    <AssignTo createNew="false" transport="http" type="request"/>
</AssignMessage>

and the relevant snippet of my flow in the main policy looks like this:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ProxyEndpoint name="default">
    <PreFlow name="PreFlow">
        <Request>
            <Step>
                <Name>FC-GET-GOOGLE-CLOUD-TOKEN</Name>
            </Step>
            <Step>
                <Name>AM-GOOGLE_TOKEN</Name>
            </Step>
        </Request>
        <Response/>
    </PreFlow>
    <Flows>
        <Flow name="ListEndpoint">
            <Description>Performs a Get operation at the Endpoint type level.</Description>
            <Request/>
            <Response/>
            <Condition>(proxy.pathsuffix MatchesPath "/Endpoint") and (request.verb = "GET")</Condition>
        </Flow>

I have done a trace and the jwt is there and it is set to the Authorization header correclty as shown it the attached image.

10921-header-and-jwt.png

Is it something with my JWT generator, or is it something else?

Thanks again for your support!

Richard Braman

FHIRFLY

Solved Solved
0 5 956
1 ACCEPTED SOLUTION

It's something else. The policy that you use to generate the self-signed JWT looks fine to me. I think there is some confusion on which token goes where.

From the doc page you listed, this diagram shows the flow:

10922-screenshot-20210401-093736.png

In English,

  1. a system must create (and sign) a JWT
  2. the system sends the JWT to Google's Token dispensary endpoint (https://oauth2.googleapis.com/token)
  3. That token endpoints responds with a Bearer token
  4. that bearer token can then be used when making request to other endpoints, for example healthcare.googleapis.com

There are details missing from this description, obviously. In particular, the request in step 2 "Send the JWT to the token dispensary endpoint", must be a POST, and it must have a particular form.

POST https://oauth2.googleapis.com/token
content-type:application/x-www-form-urlencoded

grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion=JWT_HERE

The self-signed JWT gets sent as a FORM parameter. It is not a Bearer token, and in no case should you place the self-signed JWT into an Authorization header. I don't know of any system that will accept that self-signed JWT when passed in that way.

The self-signed JWT is a token, but is not a bearer token. It is a token that you use to request a bearer token. So you see there are two distinct tokens you must deal with. The first is used in the token-grant request. The response to the token-grant request includes another token (as it happens, it is NOT a JWT), which you can then use as a Bearer token (place it in the Authorization header)

Does this help?

View solution in original post

5 REPLIES 5

One thing I just noticed is that there is no kid header in the generate JWT policy. But I added it like so and it did not affect the result

    <PrivateKey>
        <Value ref="private.private_key"/>
        <Id>xxxxxxxxxxxxxxxxxxxxxxxxx</Id>
    </PrivateKey>

It's something else. The policy that you use to generate the self-signed JWT looks fine to me. I think there is some confusion on which token goes where.

From the doc page you listed, this diagram shows the flow:

10922-screenshot-20210401-093736.png

In English,

  1. a system must create (and sign) a JWT
  2. the system sends the JWT to Google's Token dispensary endpoint (https://oauth2.googleapis.com/token)
  3. That token endpoints responds with a Bearer token
  4. that bearer token can then be used when making request to other endpoints, for example healthcare.googleapis.com

There are details missing from this description, obviously. In particular, the request in step 2 "Send the JWT to the token dispensary endpoint", must be a POST, and it must have a particular form.

POST https://oauth2.googleapis.com/token
content-type:application/x-www-form-urlencoded

grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion=JWT_HERE

The self-signed JWT gets sent as a FORM parameter. It is not a Bearer token, and in no case should you place the self-signed JWT into an Authorization header. I don't know of any system that will accept that self-signed JWT when passed in that way.

The self-signed JWT is a token, but is not a bearer token. It is a token that you use to request a bearer token. So you see there are two distinct tokens you must deal with. The first is used in the token-grant request. The response to the token-grant request includes another token (as it happens, it is NOT a JWT), which you can then use as a Bearer token (place it in the Authorization header)

Does this help?

Dino, I had modified your GCP token proxy to fit the sequence diagram you posted above and I agree that I have never seen a system take a signed JWT as the bearer token. But the document I reference above says (ver batum):

Call the API, using the signed JWT as the bearer token:

GET /v1/projects/abc/databases/123/indexes HTTP/1.1
Authorization: Bearer SIGNED_JWT
Host: firestore.googleapis.com									

I understand your flow control diagram above quite well having implemented 100s of APIs, but either the article above is in error, or Google has designed another way, or I am misunderstanding because of the way it is written.

It also says (maybe Healthcare API is not one of "some Google Apis"):

With some Google APIs, you can make authorized API calls using a signed JWT directly as a bearer token, rather than an OAuth 2.0 access token. When this is possible, you can avoid having to make a network request to Google's authorization server before making an API call.								

Oh I see !

I was not aware.

I thought that ALL of the apis available on googleapis.com required a bearer token. my mistake! Thanks for showing me the facts.

I am pretty sure that the healthcare.googleapis.com will require a bearer token.

I am pretty sure you are right. I went back to doing it with the excellent GCP Token proxy. It works great!