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
});
};