Firebase/google cloud functions, APIGee and api key auth

Hey all,

So I have experience writing the actual logic of microservices but I'm pretty new to the art of rigging them together and the whole API Management/Gateway/Proxy/Specification ecosystem(s) have been doing my head in for the past week or so.

I'm trying to build an API to face organizational clients. I'd like to eventually offer this in terms of an organization holding a set of individually revokable API keys of different roles, but to get something out I'm just aiming to have one API key per org.

I have a definition, in both OpenApi 2 and 3, it's a REST-y kind of api - GET /resourceAs/{uuid}/subResourceB/{uuid} and I'd like to implement it with firebase/google functions cos that's my wheelhouse, but I'm happy to try other backend platforms if they work better. The stumbling block I feel like I have hit is in configuring org-level permissions - that is I need to somehow communicate in a secure manner to the backend service that APIGee has authenticated the request as containing an API key owned by organization X (where my backend service can then handle the implication that Organization X owns the resource at /resourceAs/{uuid}/subResourceB/{uuid}).

I feel like the answer is some combination of JWT and or opaque tokens? I don't want the user's to need to have completed authentication with me other than their possession of an API key transmitted in the headers. I had a look at the Google cloud functions extension but it seems like seeing as it only trigger's HTTPS functions I can't just trust (from the backend's perspective) content added to the headers/payload sent in the Policy there (such as the custom attribute from a manually registered developer's App) - or am I wrong in thinking that?

Essentially I am looking for a way to assert some claims associated with a particular App's API key, in a secure way to my backend service. Can anyone help a hapless developer thrown into the devops arena?

Solved Solved
1 4 947
1 ACCEPTED SOLUTION

There are numerous ways to communicate that information from an API Gateway like Apigee Edge into a backend system like Firebase or Google Cloud Functions. (or AppEngine, or etc)

First, the information is knowable in the API Proxy.

if the inbound call is carrying an access token, then the API Proxy should call VerifyAccessToken. After successful verification, there will be context variables populated including

  • developer.id
  • developer.app.name
  • client_id

(see the doc for full details)

That perhaps isn't quite the information that you seek when you say "Organization X". But take heart, there is more power here.

  1. any access token can have "custom attributes" associated to it at the time it is issued. If you use a password grant or a authorization code grant, then at the time you use GenerateAccessToken policy, you can attach user-oriented attributes including "Organization" or "team" or "user role" or whatever is appropriate for your scenario. Presumably this information is knowable at the time you are generating the token, because you've used an IdP that stores such information. After VerifyAccessToken, these custom attributes are available in context variables with names like accesstoken.{custom_attribute}.
  2. If you don't want or need an attribute associated to the authenticated _user_, you also have the option of attaching custom attributes to the API Product. These attributes will also be available in context variables after VerifyAccessToken, but the context variables will hold the same information for all tokens issued for that API product.
  3. You can also attach attributes to the developer. (Works the same way)

Second, you can augment the request.

The easiest way to attach the information about "organiztion X" to a request that the proxy sends to the backend is sometimes known as "header injection". The API Gateway can simply add a new header to the request that is sent to the backend, and this header can contain whatever you like. Do this with AssignMEssage and a Set element.

<AssignMessage name="AM-InjectHeader'>
  <Set>  
    <Headers>
      <Header name='my-new-header'>{variable_name_from_the_token}</Header>
    </Headers>
  </Set> 
</AssignMessage>

If you want additional information in the header, the "text value" in the XML config is a Message Template. This means you can refer to multiple distinct variables within curly braces and the value of the header is then derived from all those referenced variables.

If the API proxy receives a request like this:

POST /v1/foo/bar
Content-Type: application/json
Accept: */*
Authorization: Beadrer ABC12345

The backend will see something like this:

POST /v1/foo/bar
Content-Type: application/json
Accept: */*
Authorization: Bearer ABC12345
My-New-Header: value-of-referenced-variable-here

You can also (probably should) remove the token before invoking the backend. You can o this with assignMEssage also:

<AssignMessage name="AM-RemoveAuthzHeader'>
  <Remove>  
    <Headers>
      <Header name='authorization'/>
    </Headers>
  </Remove> 
</AssignMessage>

And optionally you can combine those AssignMessage policies into one:

<AssignMessage name="AM-AdjustHeaders'>
  <Remove>  
    <Headers>
      <Header name='authorization'/>
    </Headers>
  </Remove>
 <Set>  
    <Headers>
      <Header name='my-new-header'>{variable_name_from_the_token}</Header>
    </Headers>
  </Set> 
</AssignMessage>

Third, you can embed the information in a signed JWT

You mentioned a JWT. The value of "my-new-header' can just as easily be a JWT, generated within the Apigee Edge proxy via the GenerateJWT policy, asserting various claims. You would do this if you wanted an extra level of security in the backend; it can verify the signature on the JWT before trusting the claims within it.

In my experience, this is more than is necessary, unless you have a somewhat complex dependency graph in your services mesh, in which service A calls B, which calls C, which calls D and E and F, and some of those call G and H and so on. And each service may wish to assert some claims. In that case a receiving service might want to understand which of the previous services has asserted claims, and you could use a signed JWT to verify the integrity and issuer of claims, at any point in the graph, in a zero-trust environment.

But often that is more than you need. Often a simple text header saying "Organization X" is suitable and satisfactory.

View solution in original post

4 REPLIES 4

There are numerous ways to communicate that information from an API Gateway like Apigee Edge into a backend system like Firebase or Google Cloud Functions. (or AppEngine, or etc)

First, the information is knowable in the API Proxy.

if the inbound call is carrying an access token, then the API Proxy should call VerifyAccessToken. After successful verification, there will be context variables populated including

  • developer.id
  • developer.app.name
  • client_id

(see the doc for full details)

That perhaps isn't quite the information that you seek when you say "Organization X". But take heart, there is more power here.

  1. any access token can have "custom attributes" associated to it at the time it is issued. If you use a password grant or a authorization code grant, then at the time you use GenerateAccessToken policy, you can attach user-oriented attributes including "Organization" or "team" or "user role" or whatever is appropriate for your scenario. Presumably this information is knowable at the time you are generating the token, because you've used an IdP that stores such information. After VerifyAccessToken, these custom attributes are available in context variables with names like accesstoken.{custom_attribute}.
  2. If you don't want or need an attribute associated to the authenticated _user_, you also have the option of attaching custom attributes to the API Product. These attributes will also be available in context variables after VerifyAccessToken, but the context variables will hold the same information for all tokens issued for that API product.
  3. You can also attach attributes to the developer. (Works the same way)

Second, you can augment the request.

The easiest way to attach the information about "organiztion X" to a request that the proxy sends to the backend is sometimes known as "header injection". The API Gateway can simply add a new header to the request that is sent to the backend, and this header can contain whatever you like. Do this with AssignMEssage and a Set element.

<AssignMessage name="AM-InjectHeader'>
  <Set>  
    <Headers>
      <Header name='my-new-header'>{variable_name_from_the_token}</Header>
    </Headers>
  </Set> 
</AssignMessage>

If you want additional information in the header, the "text value" in the XML config is a Message Template. This means you can refer to multiple distinct variables within curly braces and the value of the header is then derived from all those referenced variables.

If the API proxy receives a request like this:

POST /v1/foo/bar
Content-Type: application/json
Accept: */*
Authorization: Beadrer ABC12345

The backend will see something like this:

POST /v1/foo/bar
Content-Type: application/json
Accept: */*
Authorization: Bearer ABC12345
My-New-Header: value-of-referenced-variable-here

You can also (probably should) remove the token before invoking the backend. You can o this with assignMEssage also:

<AssignMessage name="AM-RemoveAuthzHeader'>
  <Remove>  
    <Headers>
      <Header name='authorization'/>
    </Headers>
  </Remove> 
</AssignMessage>

And optionally you can combine those AssignMessage policies into one:

<AssignMessage name="AM-AdjustHeaders'>
  <Remove>  
    <Headers>
      <Header name='authorization'/>
    </Headers>
  </Remove>
 <Set>  
    <Headers>
      <Header name='my-new-header'>{variable_name_from_the_token}</Header>
    </Headers>
  </Set> 
</AssignMessage>

Third, you can embed the information in a signed JWT

You mentioned a JWT. The value of "my-new-header' can just as easily be a JWT, generated within the Apigee Edge proxy via the GenerateJWT policy, asserting various claims. You would do this if you wanted an extra level of security in the backend; it can verify the signature on the JWT before trusting the claims within it.

In my experience, this is more than is necessary, unless you have a somewhat complex dependency graph in your services mesh, in which service A calls B, which calls C, which calls D and E and F, and some of those call G and H and so on. And each service may wish to assert some claims. In that case a receiving service might want to understand which of the previous services has asserted claims, and you could use a signed JWT to verify the integrity and issuer of claims, at any point in the graph, in a zero-trust environment.

But often that is more than you need. Often a simple text header saying "Organization X" is suitable and satisfactory.

Hey Dino,

Thanks very much, this is a lot like what my first thought had been but my worry was the ability to trust information in the headers on the backend came from a trusted source (in this case APIGee) - Would I be correct in thinking that the steps for configuring two way TLS would provide that surety? Although actually I am not sure I can have much control over the certificate GCF use for https connections. That's why I was thinking maybe I needed a JWT to be able to trust this from the backend's PoV, but was kind of hoping there was a simpler way.

Yes, correct. The 2-way TLS would assure the upstream (backend) that the request was surely coming from a peer that holds the key. If you provision the key only once - to the Apigee Edge endpoint - then TLS will insure:

  • all calls bearing that identity will be from Apigee Edge
  • all requests have not been modified between the sender and receiver

It's possible that you don't have control over the Google Cloud Functions TLS config. I'm not familiar enough to comment. [ Edit - just heard back from the PM For Google Cloud Functions. He tells me there is no way to configure mutual TLS for GCF. You get 1-way TLS for free]

In that case, sure, you can bubble up to the application layer and use the same principle on a JWT. Instead of relying on the transport layer security, you would be relying on application layer security. And here again the foundaiton of trust is that the Apigee Edge endpoint is the only holder of the private key used to sign the JWT.

was kind of hoping there was a simpler way.

It's actually pretty simple to create a JWT within Apigee Edge and then inject it as a header into the request that gets sent to the backend. You'd need 2 policies.

First GenerateJWT.

<GenerateJWT name='GenerateJWT-1'>
  <Algorithm>PS256</Algorithm>
  <IgnoreUnresolvedVariables>false</IgnoreUnresolvedVariables>
  <PrivateKey>
    <Value ref="private.key"/>
  </PrivateKey>
  <Subject ref="jwt_subject" />
  <Issuer>urn://my-apigee-edge-proxy</Issuer>
  <AdditionalClaims>
    <Claim name='user_organization' ref='name_of_context_variable_holding_information'/>
  </AdditionalClaims>
  <ExpiresIn>1h</ExpiresIn>
  <OutputVariable>generated_jwt</OutputVariable>
</GenerateJWT>
<br>

And then the AssignMessage

<AssignMessage name='AM-1'>
  <Set>
    <Headers>
      <Header name='my-header'>{generated_jwt}</Header>
    </Headers>
  </Set>
</AssignMessage>

If you put that in the request flow, then the request sent to the backend will have a new header, containing a JWT, signed by Apigee Edge. It would be up to the Cloud Function to verify the signature and then extract the claims and do something with that JWT.

Amazing, thanks very much for the in depth and specific help!