Rate limiting for Google/Firebase cloud functions?
Doing this on a per-user basis sounds fairly straightforward:
- Pass the ID token of the user to Cloud Functions with each request.
- Decode the ID token in your Cloud Function to determine the UID. For an example of these first two steps, see the
functions-samples
repo. - Push the fact that user UID has called the function to a database, probably adding it to a list. E.g.
admin.database().ref(`/userCalls/$uid`).push(ServerValue.TIMESTAMP)
. - Query for the number of recent calls with something like
admin.database().ref(`/userCalls/$uid`).orderByKey().startAt(Date.now()-60000)
. - Count the results and reject if it is too high.
I'm not sure if the IP address of the caller is passed to Cloud Functions. If it is, you can do the same logic for the IP address. If it isn't passed, it'll be hard to rate limit in a way that can't be easily spoofed.
I made a library for rate-limiting calls to firebase functions: firebase-functions-rate-limiter The library uses realtimeDB or firestore (configurable) as a backend. It stores data, in a similar approach that Frank described, but is more economical. Instead of using a collection, it uses a single document with array per each qualifier (eg. a user id). That means there is only a single read for an exceeded call, and a read-write for an allowed call.
$ npm i --save firebase-functions-rate-limiter
Here is an example:
import * as admin from "firebase-admin";
import * as functions from "firebase-functions";
import { FirebaseFunctionsRateLimiter } from "firebase-functions-rate-limiter";
admin.initializeApp(functions.config().firebase);
const database = admin.database();
const limiter = FirebaseFunctionsRateLimiter.withRealtimeDbBackend(
{
name: "rate_limiter_collection",
maxCalls: 2,
periodSeconds: 15,
},
database,
);
exports.testRateLimiter =
functions.https.onRequest(async (req, res) => {
await limiter.rejectOnQuotaExceeded(); // will throw HttpsException with proper warning
res.send("Function called");
});