Limit the number of M2M token exchanges per application

Problem statement

How can we limit the number of M2M token exchanges per application?

Solution

There is no standard functionality that allows you to configure rate limits for machine-to-machine token exchanges on a per-application basis.

The only solution would be to use a client credentials hook or action that stores usage per application in your data store and checks if the usage has been exceeded. You can use the below scripts as a reference for your implementation.

/**
@param {object} client - information about the client
@param {string} client.name - name of client
@param {string} client.id - client id
@param {string} client.tenant - Auth0 tenant name
@param {object} client.metadata - client metadata
@param {array|undefined} scope - array of strings representing the scope claim or undefined
@param {string} audience - token's audience claim
@param {object} context - additional authorization context
@param {object} context.webtask - webtask context
@param {function} cb - function (error, accessTokenClaims)
*/
module.exports = function(client, scope, audience, context, cb) {
  let redis     = require('redis');
  let redisClient    = redis.createClient({
    port      : 6379, // TODO: use 6380 for TLS
    host      : 'n8y44i.stackhero-network.com',
    auth_pass : 'YOUR_PASSWORD',
    no_ready_check: true
  });
 
  var timePeriodToMonitor = 1000 * 60 * 1; //  minutes
  var rateLimitPerTimePeriod = 5;
 
  redisClient.lrange(client.id, 0, -1, function(err, clientHistory) {
      if (err) {
        console.log(err);
        throw err;
      } else {     
        if (clientHistory === null) clientHistory = [];
 
        // check if limit has been reached
        var now = new Date().getTime();
        var requestsInTimePeriod = 0;
        var outdatedIndex = -1;
        for (var i = 0; i < clientHistory.length; i++) {
            if (now - clientHistory[i] < timePeriodToMonitor) {
              requestsInTimePeriod++;
            } else {
              outdatedIndex = i;
            }
        }
         
        // Throw error if rate limit is exceeded
        if (requestsInTimePeriod >= rateLimitPerTimePeriod) {
          cb('Rate limit reached');
        } else {
          clientHistory.push(now);
          cb(null, {}); // no error, proceed
        };
         
        // adding the current time to the rateLimitCnt history array
        if (requestsInTimePeriod < rateLimitPerTimePeriod) {
            redisClient.rpush(client.id, now, function(error) {
            if (error) {
              console.log('Error writing to Redis: ' + error);
              //return cb(error);
              throw error;
            }
                
            //remove outdated entries
            if (outdatedIndex >= 0) {
              redisClient.ltrim(client.id, outdatedIndex + 1, -1, function(error) {
                if (error) {
                  console.log('Error trimming list: ' + error);
                  throw error;
                }
              });
               
            }
          });
        }
      } // if err
  });
};