Apigee Edge Proxy authentication - MS Entra

We are an Apigee Edge enterprise customer. Most of the proxies that we published on the Apigee Edge use oAuth2.0 authentication between the consumer & Apigee proxy.

Consumer (system)[1] <-> Apigee[2] <-> Target endpoint[3].

  • We provided the client ID, Secret from the Apigee[2] (used as authorization server) to the consumer[1]. Consumers[1] generate the access token before calling the Apigee proxy[2].
  • If the access key is valid, the Apigee proxy[2] uses API key authentication while connecting to the Target endpoint[3].

There is a new use case, where the consumer[1] doesn’t want to use the Apigee[2] as an authorization server. Instead, they want to use Microsoft Entra.

The team will provide the Application identity (principal) to the Apigee proxy[2], we need to validate the identity(access token) of the consumer[1]. If it is valid, Apigee should make a call to the Target endpoint[3].

Can anyone please provide some guidance - how to implement the above? @dchiesa1 Thanks in advance.

1 5 183
5 REPLIES 5

the consumer[1] doesn’t want to use the Apigee[2] as an authorization server. Instead, they want to use Microsoft Entra.

The team will provide the Application identity (principal) to the Apigee proxy[2], we need to validate the identity(access token) of the consumer[1]. If it is valid, Apigee should make a call to the Target endpoint[3].

This is pretty straightforward. There are some nuances.

As far as I understand Entra ID will act as an OIDC provider. The consumer connects to EntraID , goes through the OIDC token grant process, and the result is an ID token and an access token.  In most cases with OIDC that I have seen, both tokens are JWT, signed with a private RSA key. 

Apigee includes a VerifyJWT policy. If you configure the VerifyJWT policy to point to the JWKS endpoint of the EntraID tenant, then, Apigee can retrieve the public keys for EntraID, and verify the access token.  If VerifyJWT succeeds, then the token is good, authentic, not expired, and issued by EntraID.  Which means your Apigee proxy can "trust" the claims in the token. you can configure the proxy, At that point , to call the upstream/backend/target system. 

Now for the nuance. One of the cool things about Apigee is the API Product concept. If you just configure Apigee to verify a JWT that was issued by some other issuer, that will "work", but it doesn't have any reliance on the API product. Which means you get none of the API product coolness, like automatic authorization checks for the operation (the {verb,path} pair), or metadata like Quota / rate limits or other custom attributes. You don't get the API analytics.  

To solve this , you can synchronize the clientid that you use in EntraID, with the clientID in Apigee.  Create the client app in EntraID, and then "import" that clientID as a "consumer key" into Apigee, with authorization for one or more API products. Then, you can configure the proxy to:

  • VerifyJWT as above, with the EntraID JWKS endpoint
  • VerifyAPIKey on the extracted clientid claim from that JWT
  • and only THEN, proxy to the target

 

Hi @dchiesa1 

I'm also referring to this thread for the same use case. Would you mind clarifying the last part about solving the nuance? And are there any documentations on how to import the clientID from Entra ID into Apigee?

Thanks.

For importing a credential, you can use the documented REST API

POST :apigee/v1/organizations/:org/developers/:dev/apps/:app/keys/create
Authorization: Bearer :token
content-type: application/json

{
  "apiProducts": ["Product1", "Product2"],
  "consumerKey": "REQUIRED",
  "consumerSecret": "REQUIRED",
  "expiresInSeconds": "864000"
}

The Developer, and the App , must already exist. 

In the payload above, the consumerKey is the "client ID" that you get from Entra ID.  I don;t know how you'd get that, but I guess there is an API you can use to find that thing. 

The consumerSecret may also be something that EntraID offers, but ... you probably don't need that, since the security of the credential depends on the signature provided by EntraID on the JWT ID token.  So the consumerSecret in Apigee is essentially unused. You could place any string in there, I would probably use something like "not-needed" or similar. (not sure if there is a minimum character length for that secret.) 

The expiresInSeconds can be -1 for "never" or some quantity, if you like to enforce rotation of credentials. Probably if entraID is the system of record, then you don't want Apigee to manage an expiry of the credential.

If the app exists, then ... it probably already has a credential associated to it.  Apigee has an interesting model - it allows a single "app registration" to have multiple credentials (consumer key/secret pairs), and each has an independent lifetime. This supports credential rotation, while keeping the app the same entity. So any custom attributes are tied to the app, which remains the same, as opposed to the credential, which may change.  Anyway, if you are using EntraID to provide the consumerKey, what you ought to do is remove any of the OTHER credentials attached to the app. 

 I think you can do this: 

DELETE :apigee/v1/organizations/:org/developers/:dev/apps/:app/keys/:clientid_to_remove
Authorization: Bearer :token

What some people have done is, tap into the app provisioning motion, whatever that is.  Often it's done as part of an interaction with a developer portal.  When that happens, you can trigger a process that 

  • Calls into EntraID to provision a new client
  • retrieves the client id for that app from EntraID
  • provisions the new EntraID-supplied clientID into Apigee
  • removes other clientIDs (keys) from Apigee for that specific app
  • displays the updated clientID to the user on the dev portal

 There is no webhook, sadly, for the event of "an app has been created" in Apigee. but, you have options:

  1. provision an eventarc trigger that watches the GCP Logs for the "Apigee app created" log event, and then kicks off a process like I described above.
  2. run a nightly or hourly scan for new apps, and do the process in a loop for any newly provisioned apps. 

You might need both (belt & suspenders) to insure reliable behavior.

Conversely if you are not using the Apigee dev portal to provision apps, and instead you are using some UI that is connected to EntraID to provision apps, then, the process is somewhat different

  • when a new client is provisioned in EntraID
  • get the client ID
  • create a NEW app in Apigee
  • remove the existing credential (when you create an app, Apigee implicitly creates the first credential (key/secret pair) with random values)
  • add the new "key" (client ID) from Entra into that Apigee app

If you are invested in GCP, you might want to look at Application Integration , to manage the process that interrogates or updates the various systems involved. 

Hi @dchiesa1 

We implemented the "Verify JWT" policy (below is the source code) & got an error "Invalid token: policy(Verify-JWT)"
 
Here are the findings,
- While decoded the JWT Token in https://jwt.io/, at bottom it is stating that "Invalid Signature". We used RS256. 
- If we use HS256, we got valid signature in jwt.io. And accordingly we changed the policy to use the HS256 with PrivateKey (SecretKey in the policy), still get the same error "Invalid token".
 
Could you please guide - how to fix it?
 
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<VerifyJWT async="false" continueOnError="false" enabled="true" name="Verify-JWT">
    <DisplayName>Verify JWT</DisplayName>
    <Algorithm>RS256</Algorithm>
    <Source>request.header.jwt-token</Source>
    <PublicKey>
    </PublicKey>
    <Subject>XXXXX</Subject>
    <Issuer>XXXXX</Issuer>
    <Audience ref="request.header.Host"/>
</VerifyJWT>

Thank you @dchiesa1 . I got it, could you please provide any documentation to implement this pattern.