How to validate Quota Policy against JWT

I have a requirement to run Quota limits, against the JWT sent by clients

Viz, if cleint1 sends JWT as JWT1 and client2 sends JWT as JWT2 we will have run quota limits against the JWT's. And at times for certain proxies, we have to do validate quota's against the json contents of the payload.

How can this be achieved, any examples please ?

Thanks

Solved Solved
1 20 680
1 ACCEPTED SOLUTION

You can accomplish what you want using multiple distinct Apigee policies.

In the description, you didn't say that you want to validate the inbound JWT, but even if you didn't state it, that is probably what you want to do. If you don't validate the JWT, then the API proxy will accept any JWT, and in fact any client could send in anything and assert "this is a JWT", and your proxy would allow it. That would be bad. So it seems safe to assume you will want your API proxy to validate the JWT. I'll assume we are discussing a signed JWT for the purposes of this answer.

Validating means

  • verifying that the signature has been created by a trusted party. (The public key is trusted)
  • verifying that the JWT is not expired, and is not being presented before the not-before time
  • verifying any additional claims in the JWT - like audience, or issuer, etc

You can do all of that stuff with the VerifyJWT policy. It might look like this:

<VerifyJWT name='VJWT-1'>
  <Algorithm>RS256</Algorithm>
  <!-- omit the Source element if the JWT is in the Authorization header -->
  <Source>inbound.jwt</Source>
  <IgnoreUnresolvedVariables>false</IgnoreUnresolvedVariables>
  <PublicKey>
    <JWKS uri='https://my-jwks.endpoint.com/jwks.json'/>
  </PublicKey>
  <Issuer>something.example.com</Issuer>
  <Audience>urn://example.com/my-desired-audience</Audience>
</VerifyJWT>

Next you probably want to validate that the JWT is good for this particular API. In Apigee, APIs are exposed as Products, and in order to check that an inbound request should be allowed, You can use a credential verification policy in Apigee to check that the presented credentials are good for an API Product which includes the given API proxy (or the given API verb + path pair). To do this in Apigee, you can use either the OAuthV2 policy configured with the VerifyAccessToken Operation, or the VerifyAPIKey policy. Use the former when the app credentials are in the form of an opaque access token. Use the latter when the app credentials are in the form of an API Key.

In the case of a signed JWT, typically one of the claims carries the Api key. This might be a claim named client_id, or something like that. In that case, you can use VerifyAPIKey passing the variable name that contains that decoded claim.

<VerifyAPIKey name='VAPIKEY-1'>
  <!-- check that the client id embedded in the JWT is good for this API -->
  <APIKey ref='jwt.VJWT-1.decoded.claim.client_id'/>
</VerifyAPIKey>

OK, with those two policies we have

  • verified that the JWT can be trusted and has valid dates
  • verified that the client ID embedded in the JWT is good for THIS api product

What remains now is to enforce the rate limit.

You said you want to enforce a quota (rate limit), using a different quota bucket for each... ? each what? Your original question was "for each JWT" but I think that is probably not the right way to do it. A client might obtain a new JWT with each request, and each new token gets a new bucket, a new quota count allocation. That's probably not what you want.

Instead, you can enforce a quota on the client id, which will remain constant even if the client obtains new JWT periodically. Supposing your quota information is attached to the API Product, you can enforce the quota with this kind of Quota policy.

<Quota name="Quota-1">
  <Identifier ref="verifyapikey.VAPIKEY-1.client_id"/>
  <Interval ref="verifyapikey.VAPIKEY-1.apiproduct.developer.quota.interval">1</Interval>
  <TimeUnit ref="verifyapikey.VAPIKEY-1.apiproduct.developer.quota.timeunit">hour</TimeUnit>
  <Allow count="5"
         countRef="verifyapikey.VAPIKEY-1.apiproduct.developer.quota.limit"/>
  <Distributed>true</Distributed>
  <Synchronous>false</Synchronous>
</Quota>

If you want a "hard coded" quota settings, then you can use this simpler policy configuration instead:

<Quota name="Quota-1">
  <Identifier ref="verifyapikey.VAPIKEY-1.client_id"/>
  <Interval>1</Interval>
  <TimeUnit>hour</TimeUnit>
  <Allow count="5"/>
  <Distributed>true</Distributed>
  <Synchronous>false</Synchronous>
</Quota>

In any case, you will need three policies. The flow would be something like this:

<!-- verify the JWT -->
<Step>
  <Name>VJWT-1</Name>
</Step>
<!-- check that the client id embedded in the JWT is good for this API -->
<Step>
  <Name>VAPIKEY-1</Name>
</Step>
<!-- enforce a quota based on the client id -->
<Step>
  <Name>Quota-1</Name>
</Step>


View solution in original post

20 REPLIES 20

Not applicable

There is a class concept in quota, if you want different quota values then you can use that. If you want same quota value but separate counter for each jwt token then you can use identifier.

Thank you for the quick response. I'm looking to use separate counter for each JWT token. Can you please share some examples for the quota policy ? and do we have to extract the JWT token with an assign policy before calling quoat policy ?

Being a newbie to apigee, sample code will be of great help!

I don't need to extract the token for quota. simply the identifier can be configured as request.header.Authorization.

<Quota name="Quota-1">
  <Identifier ref="request.header.Authorization"/>
  <Interval>1</Interval>
  <TimeUnit>hour</TimeUnit>
  <Allow count="5"/>
  <Distributed>true</Distributed>
  <Synchronous>true</Synchronous>
</Quota>

@Priyadarshi Ajitav Jena I have tried, but this policy is not working as desired. And I would have to defer to Dino to explain why this method does not work

Not applicable

For separate counter, you can use identifier. Use the jwt token as identifier and configure as policy below.

<Quota name="QuotaPolicy">

<Identifier ref="request.header.jwttoken"/>

<Interval>5</Interval>

<TimeUnit>hour</TimeUnit>

<Allow count="99"/>

</Quota>

Thank you for the quick update Priyadarshi, I have modified the policy to allow 5 requests in a span of one hour, however the policy is not behaving as expected

<Quota name="QuotaPolicy"> <Identifier ref="request.header.jwttoken"/> <Interval>1</Interval> <TimeUnit>hour</TimeUnit> <Allow count="5"/> </Quota>

Issue 1 : There have been instances where apigee allowed the request and enforced the quota limit intermittently. (used same JWT)

10983-tracesession.png

Issue 2 : This is of major concern, even when we change the JWT token, it is not allowing the request. Per the approach, if there is a new token, that should be allowed to pass through the request. However, all the requests have been blocked

Add these two lines to policy

<Distributed>true</Distributed>

<Synchronous>true</Synchronous>

Are you sending the jwt token in the request header jwttoken? This should be there in the identifier.

Thanks @Priyadarshi Ajitav Jena, the distributed and synch parameters did the trick with blocking requests.

However we still cannot get the new token to be working as desired. And we are publishing the Bearer token in headers with Authorization parameter

10985-tracesession.png

Can you show the complete configuration for your quota policy please?

@Dino-at-Google

Below is the policy

<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <Quota name="QuotaPolicy"> <Identifier ref="request.header.jwttoken"/> <Interval>1</Interval> <TimeUnit>hour</TimeUnit> <Allow count="5"/> <Distributed>true</Distributed> <Synchronous>true</Synchronous> </Quota>

We are trying to execute a simple POC, restrict the quota based on JWT. One specific JWT can access the API only a certain number of times

10986-tracesession.png

This snip of your configuration

 <Identifier ref="request.header.jwttoken"/>

...tells the Quota policy to look for a header in the request called "JWTTOKEN". Do you have that header? I think you are passing the JWT in the Authorization header as a bearer token. In that case, the "request.header.jwttoken" will be empty, which means you will get the same identifier for all inbound requests. That would give the results you are observing.

If you change that to "request.header.authorization", you will get a different quota bucket counter for each distinct JWT. JWT1 will get its quota counted separately from JWT2.

But I think that is ALSO not what you should be doing.

See my answer for some further discussion.

You can set the identifier as request.header.Authorization

You can accomplish what you want using multiple distinct Apigee policies.

In the description, you didn't say that you want to validate the inbound JWT, but even if you didn't state it, that is probably what you want to do. If you don't validate the JWT, then the API proxy will accept any JWT, and in fact any client could send in anything and assert "this is a JWT", and your proxy would allow it. That would be bad. So it seems safe to assume you will want your API proxy to validate the JWT. I'll assume we are discussing a signed JWT for the purposes of this answer.

Validating means

  • verifying that the signature has been created by a trusted party. (The public key is trusted)
  • verifying that the JWT is not expired, and is not being presented before the not-before time
  • verifying any additional claims in the JWT - like audience, or issuer, etc

You can do all of that stuff with the VerifyJWT policy. It might look like this:

<VerifyJWT name='VJWT-1'>
  <Algorithm>RS256</Algorithm>
  <!-- omit the Source element if the JWT is in the Authorization header -->
  <Source>inbound.jwt</Source>
  <IgnoreUnresolvedVariables>false</IgnoreUnresolvedVariables>
  <PublicKey>
    <JWKS uri='https://my-jwks.endpoint.com/jwks.json'/>
  </PublicKey>
  <Issuer>something.example.com</Issuer>
  <Audience>urn://example.com/my-desired-audience</Audience>
</VerifyJWT>

Next you probably want to validate that the JWT is good for this particular API. In Apigee, APIs are exposed as Products, and in order to check that an inbound request should be allowed, You can use a credential verification policy in Apigee to check that the presented credentials are good for an API Product which includes the given API proxy (or the given API verb + path pair). To do this in Apigee, you can use either the OAuthV2 policy configured with the VerifyAccessToken Operation, or the VerifyAPIKey policy. Use the former when the app credentials are in the form of an opaque access token. Use the latter when the app credentials are in the form of an API Key.

In the case of a signed JWT, typically one of the claims carries the Api key. This might be a claim named client_id, or something like that. In that case, you can use VerifyAPIKey passing the variable name that contains that decoded claim.

<VerifyAPIKey name='VAPIKEY-1'>
  <!-- check that the client id embedded in the JWT is good for this API -->
  <APIKey ref='jwt.VJWT-1.decoded.claim.client_id'/>
</VerifyAPIKey>

OK, with those two policies we have

  • verified that the JWT can be trusted and has valid dates
  • verified that the client ID embedded in the JWT is good for THIS api product

What remains now is to enforce the rate limit.

You said you want to enforce a quota (rate limit), using a different quota bucket for each... ? each what? Your original question was "for each JWT" but I think that is probably not the right way to do it. A client might obtain a new JWT with each request, and each new token gets a new bucket, a new quota count allocation. That's probably not what you want.

Instead, you can enforce a quota on the client id, which will remain constant even if the client obtains new JWT periodically. Supposing your quota information is attached to the API Product, you can enforce the quota with this kind of Quota policy.

<Quota name="Quota-1">
  <Identifier ref="verifyapikey.VAPIKEY-1.client_id"/>
  <Interval ref="verifyapikey.VAPIKEY-1.apiproduct.developer.quota.interval">1</Interval>
  <TimeUnit ref="verifyapikey.VAPIKEY-1.apiproduct.developer.quota.timeunit">hour</TimeUnit>
  <Allow count="5"
         countRef="verifyapikey.VAPIKEY-1.apiproduct.developer.quota.limit"/>
  <Distributed>true</Distributed>
  <Synchronous>false</Synchronous>
</Quota>

If you want a "hard coded" quota settings, then you can use this simpler policy configuration instead:

<Quota name="Quota-1">
  <Identifier ref="verifyapikey.VAPIKEY-1.client_id"/>
  <Interval>1</Interval>
  <TimeUnit>hour</TimeUnit>
  <Allow count="5"/>
  <Distributed>true</Distributed>
  <Synchronous>false</Synchronous>
</Quota>

In any case, you will need three policies. The flow would be something like this:

<!-- verify the JWT -->
<Step>
  <Name>VJWT-1</Name>
</Step>
<!-- check that the client id embedded in the JWT is good for this API -->
<Step>
  <Name>VAPIKEY-1</Name>
</Step>
<!-- enforce a quota based on the client id -->
<Step>
  <Name>Quota-1</Name>
</Step>


@Dino-at-Google

The other policies viz Verify JWT, Verify API Key are intact and are working as expected. As far as the quota is considered,

A client might obtain a new JWT with each request, and each new token gets a new bucket, a new quota count allocation. That's probably not what you want.

This is what we want exactly, and we have a specific use case around it

Thanks

ok if that's what you want, then... I suggest use ExtractVariables to extract the JWT from the Authorization header, then use that extracted variable as the identifier in the Quota policy.

<ExtractVariables name='EV-1'>
  <Source>request</Source>
  <VariablePrefix>extracted</VariablePrefix>
  <Header name='Authorization'>
    <Pattern>Bearer {jwt}</Pattern>
  </Header>
  <IgnoreUnresolvedVariables>false</IgnoreUnresolvedVariables>
</ExtractVariables>

followed by a Quota

<Quota name="Quota-1">
  <Identifier ref="extracted.jwt"/>
  <Interval>1</Interval>
  <TimeUnit>hour</TimeUnit>
  <Allow count="5"/>
  <Distributed>true</Distributed>
  <Synchronous>false</Synchronous>
</Quota>

@dino-at-Google

Thank you, this did resolve the use case we have..

Glad to hear it. As Priyadarshi points out in a different comment, you don't absolutely NEED to extract the JWT from the Authorization header. I like the idea of extracting it because... it makes it more explicit what you're rate limiting on.

@dchiesa1 - I'm considering a similar use-case in which we may want to apply quotas for individual users (either based on their access token, or more likely a unique user ID associated with their token(s)) to prevent abuse by individual authenticated users.

In this case the `client_id` wouldn't be sufficiently granular as it would apply across all instances of a given developer app (for example the organisation's Android app) and any quota exhaustion would affect all users on using that developer app.

We have a large number of active users at peak, so Apigee would need to maintain a significant number of individual counters for these quotas if we take this approach. Are there any performance or cost implications we should be aware of when considering this?

For wider context, this is inspired by some of the approaches listed in this article used by Github, LinkedIn & Bitly to apply quotas for authenticated users: https://nordicapis.com/everything-you-need-to-know-about-api-rate-limiting/#bestpracticesforapiratel...

This is an interesting question and I would like you to ask it as a new question, s it does not get buried in this one-year old discussion. Can you do that? 

ask-a-new-question.png