Throttle the Rate of Requests to Protected Applications

To protect applications from being overused by clients, use a throttling filter to limit how many requests can be made in a defined time. The maximum number of requests that are allowed in a defined time is called the throttling rate. The following sections describe how to set up simple, mapped, and scriptable throttling filters:

About Throttling

The throttling filter uses the token bucket algorithm, allowing some unevenness or bursts in the request flow. The following image shows how IG manages requests for a throttling rate of 10 requests/10 seconds:

IG allows concurrent requests up to the maximum number defined by the throttling rate, and then allows requests at a constant rate defined by the throttling rate.
  • At 7 seconds, 2 requests have previously passed when there is a burst of 9 requests. IG allows 8 requests, but disregards the 9th because the throttling rate for the 10-second throttling period has been reached.

  • At 8 and 9 seconds, although 10 requests have already passed in the 10-second throttling period, IG allows 1 request each second.

  • At 17 seconds, 4 requests have passed in the previous 10-second throttling period, and IG allows another burst of 6 requests.

When the throttling rate is reached, IG issues an HTTP status code 429 Too Many Requests and a Retry-After header like the following, where the value is the number of seconds to wait before trying the request again:

GET http://openig.example.com:8080/home/throttle-scriptable HTTP/1.1
. . .

HTTP/1.1 429 Too Many Requests
Retry-After: 10

Configure Simple Throttling

This section describes how to configure a simple throttling filter that applies a throttling rate of 6 requests/10 seconds. When an application is protected by this throttling filter, no more than 6 requests, irrespective of their origin, can access the sample application in a 10 second period.

No more than 6 requests in total, irrespective of their origin, can access the application in a 10 second period.
  1. Add the following route to IG:

    • Linux

    • Windows

    $HOME/.openig/routes/00-throttle-simple.json
    appdata\OpenIG\config\routes\00-throttle-simple.json
    {
      "name": "00-throttle-simple",
      "baseURI": "http://app.example.com:8081",
      "condition": "${matches(request.uri.path, '^/home/throttle-simple')}",
      "handler": {
        "type": "Chain",
        "config": {
          "filters": [
            {
              "type": "ThrottlingFilter",
              "name": "ThrottlingFilter-1",
              "config": {
                "requestGroupingPolicy": "",
                "rate": {
                  "numberOfRequests": 6,
                  "duration": "10 s"
                }
              }
            }
          ],
          "handler": "ReverseProxyHandler"
        }
      }
    }

    For information about how to set up the IG route in Studio, see Simple Throttling Filter in Structured Editor.

    Notice the following features of the route:

    • The route matches requests to /home/throttle-simple.

    • The ThrottlingFilter contains a request grouping policy that is blank. This means that all requests are in the same group.

    • The rate defines the number of requests allowed to access the sample application in a given time.

  2. Test the setup:

    1. With IG and the sample application running, use curl, a bash script, or another tool to access the following route in a loop: http://openig.example.com:8080/home/simple-throttle.

      Accessing the route in a loop runs the request multiple times in quick succession, allowing you to test the throttling rate.

      $ curl -v http://openig.example.com:8080/home/throttle-simple/\[01-10\] \
      > /tmp/throttle-simple.txt 2>&1
    2. Search the output file to see the result:

      $ grep "< HTTP/1.1" /tmp/throttle-simple.txt | sort | uniq -c
      
      6 < HTTP/1.1 200 OK
      4 < HTTP/1.1 429 Too Many Requests

      Notice that the first six requests returned a success response, and the following four requests returned an HTTP 429 Too Many Requests. This result demonstrates that the throttling filter has allowed only six requests to access the application, and has blocked the other requests.

Configure Mapped Throttling

This section describes how to configure a mapped throttling policy, where the grouping policy defines criteria to group requests, and the rate policy defines the criteria by which rates are mapped.

The following image illustrates how different throttling rates can be applied to users.

The following image illustrates how each user with a gold status has a throttling rate of 6 requests/10 seconds, and each user with a silver status has 3 requests/10 seconds. The bronze status is not mapped to a throttling rate, and so a user with the bronze status has the default rate.

Different throttling rates are applied to users according to the status.
  1. Set up AM:

    1. Set up AM as described in Validate Access_Tokens Through the Introspection Endpoint.

    2. Select (scripts) > OAuth2 Access Token Modification Script, and replace the default script as follows:

      import org.forgerock.http.protocol.Request
      import org.forgerock.http.protocol.Response
      
      def attributes = identity.getAttributes(["mail", "employeeNumber"].toSet())
      accessToken.setField("mail", attributes["mail"][0])
      def mail = attributes['mail'][0]
      if (mail.endsWith('@example.com')) {
        status = "gold"
      } else if (mail.endsWith('@other.com')) {
        status = "silver"
      } else {
        status = "bronze"
      }
      accessToken.setField("status", status)

      The AM script adds user profile information to the access_token, and defines the content of the users status field according to the email domain.

  2. Set up IG:

    1. Set an environment variable for the IG agent password, and then restart IG:

      $ export AGENT_SECRET_ID='cGFzc3dvcmQ='

      The password is retrieved by a SystemAndEnvSecretStore, and must be base64-encoded.

    2. Add the following route to IG:

      • Linux

      • Windows

      $HOME/.openig/routes/00-throttle-mapped.json
      appdata\OpenIG\config\routes\00-throttle-mapped.json
      {
        "name": "00-throttle-mapped",
        "baseURI": "http://app.example.com:8081",
        "condition": "${matches(request.uri.path, '^/home/throttle-mapped')}",
        "heap": [
          {
            "name": "SystemAndEnvSecretStore-1",
            "type": "SystemAndEnvSecretStore"
          },
          {
            "name": "AmService-1",
            "type": "AmService",
            "config": {
              "agent": {
                "username": "ig_agent",
                "passwordSecretId": "agent.secret.id"
              },
              "secretsProvider": "SystemAndEnvSecretStore-1",
              "url": "http://openam.example.com:8088/openam/",
              "version": "7.1"
            }
          }
        ],
        "handler": {
          "type": "Chain",
          "config": {
            "filters": [
              {
                "name": "OAuth2ResourceServerFilter-1",
                "type": "OAuth2ResourceServerFilter",
                "config": {
                  "scopes": [
                    "mail",
                    "employeenumber"
                  ],
                  "requireHttps": false,
                  "realm": "OpenIG",
                  "accessTokenResolver": {
                    "name": "token-resolver-1",
                    "type": "TokenIntrospectionAccessTokenResolver",
                    "config": {
                      "amService": "AmService-1",
                      "providerHandler": {
                        "type": "Chain",
                        "config": {
                          "filters": [
                            {
                              "type": "HttpBasicAuthenticationClientFilter",
                              "config": {
                                "username": "ig_agent",
                                "passwordSecretId": "agent.secret.id",
                                "secretsProvider": "SystemAndEnvSecretStore-1"
                              }
                            }
                          ],
                          "handler": "ForgeRockClientHandler"
                        }
                      }
                    }
                  }
                }
              },
              {
                "name": "ThrottlingFilter-1",
                "type": "ThrottlingFilter",
                "config": {
                  "requestGroupingPolicy": "${contexts.oauth2.accessToken.info.mail}",
                  "throttlingRatePolicy": {
                    "name": "MappedPolicy",
                    "type": "MappedThrottlingPolicy",
                    "config": {
                      "throttlingRateMapper": "${contexts.oauth2.accessToken.info.status}",
                      "throttlingRatesMapping": {
                        "gold": {
                          "numberOfRequests": 6,
                          "duration": "10 s"
                        },
                        "silver": {
                          "numberOfRequests": 3,
                          "duration": "10 s"
                        },
                        "bronze": {
                          "numberOfRequests": 1,
                          "duration": "10 s"
                        }
                      },
                      "defaultRate": {
                        "numberOfRequests": 1,
                        "duration": "10 s"
                      }
                    }
                  }
                }
              }
            ],
            "handler": "ReverseProxyHandler"
          }
        }
      }

      For information about how to set up the IG route in Studio, see Mapped Throttling Filter in Structured Editor.

      Notice the following features of the route:

      • The route matches requests to /home/throttle-mapped.

      • The OAuth2ResourceServerFilter validates requests with the AccessTokenResolver, and makes it available for downstream components in the oauth2 context.

      • The ThrottlingFilter bases the request grouping policy on the AM user’s email. The throttling rate is applied independently to each email address.

        The throttling rate is mapped to the AM user’s status, which is defined by the email domain, in the AM script.

  3. Test the setup:

    1. In a terminal window, use a curl command similar to the following to retrieve an access_token:

      $ mytoken=$(curl -s \
      --user "client-application:password" \
      --data "grant_type=password&username=demo&password=Ch4ng31t&scope=mail%20employeenumber" \
      http://openam.example.com:8088/openam/oauth2/access_token | jq -r ".access_token")
    2. Using the access_token, access the route multiple times. The following example accesses the route 10 times, and writes the output to a file:

      $ curl -v http://openig.example.com:8080/home/throttle-mapped/\[01-10\] \
             --header "Authorization:Bearer ${mytoken}" \
             > /tmp/throttle-mapped.txt 2>&1
    3. Search the output file to see the result:

      $ grep "< HTTP/1.1" /tmp/throttle-mapped.txt | sort | uniq -c
      
      6 < HTTP/1.1 200
      4 < HTTP/1.1 429

      Notice that with a gold status, the user can access the route 6 times in 10 seconds.

    4. In AM, change the demo user’s email to demo@other.com, and then run the last two steps again to see how the access is reduced.

Considerations for Dynamic Throttling

The following image illustrates what can happen when the throttling rate defined by throttlingRateMapping changes frequently or quickly:

The user starts out with a gold status. In a two second period, the users sends five requests, is downgraded to silver, sends four requests, is upgraded back to gold, and then sends three more requests. Each time the user’s status is changed, they are allocated the full throttling rate for the status irrespective of whether they have previously reached his throttling rate for the status.

In the image, the user starts out with a gold status. In a two second period, the users sends five requests, is downgraded to silver, sends four requests, is upgraded back to gold, and then sends three more requests.

After making five requests with a gold status, the user has almost reached their throttling rate. When his status is downgraded to silver, those requests are disregarded and the full throttling rate for silver is applied. The user can now make three more requests even though they have nearly reached their throttling rate with a gold status.

After making three requests with a silver status, the user has reached their throttling rate. When the user makes a fourth request, the request is refused.

The user is now upgraded back to gold and can now make six more requests even though they had reached his throttling rate with a silver status.

When you configure requestGroupingPolicy and throttlingRateMapper, bear in mind what happens when the throttling rate defined by the throttlingRateMapper is changed.

Configure Scriptable Throttling

This section builds on the example in Configure Mapped Throttling. It creates a scriptable throttling filter, where the script applies a throttling rate of 6 requests/10 seconds to requests from gold status users. For all other requests, the script returns null, and applies the default rate of 1 request/10 seconds.

  1. Set up AM as described in Configure Mapped Throttling.

  2. Set up IG:

    1. Set an environment variable for the IG agent password, and then restart IG:

      $ export AGENT_SECRET_ID='cGFzc3dvcmQ='

      The password is retrieved by a SystemAndEnvSecretStore, and must be base64-encoded.

    2. Add the following route to IG:

      • Linux

      • Windows

      $HOME/.openig/routes/00-throttle-scriptable.json
      appdata\OpenIG\config\routes\00-throttle-scriptable.json
      {
        "name": "00-throttle-scriptable",
        "baseURI": "http://app.example.com:8081",
        "condition": "${matches(request.uri.path, '^/home/throttle-scriptable')}",
        "heap": [
          {
            "name": "SystemAndEnvSecretStore-1",
            "type": "SystemAndEnvSecretStore"
          },
          {
            "name": "AmService-1",
            "type": "AmService",
            "config": {
              "agent": {
                "username": "ig_agent",
                "passwordSecretId": "agent.secret.id"
              },
              "secretsProvider": "SystemAndEnvSecretStore-1",
              "url": "http://openam.example.com:8088/openam/",
              "version": "7.1"
            }
          }
        ],
        "handler": {
          "type": "Chain",
          "config": {
            "filters": [
              {
                "name": "OAuth2ResourceServerFilter-1",
                "type": "OAuth2ResourceServerFilter",
                "config": {
                  "scopes": [
                    "mail",
                    "employeenumber"
                  ],
                  "requireHttps": false,
                  "realm": "OpenIG",
                  "accessTokenResolver": {
                    "name": "token-resolver-1",
                    "type": "TokenIntrospectionAccessTokenResolver",
                    "config": {
                      "amService": "AmService-1",
                      "providerHandler": {
                        "type": "Chain",
                        "config": {
                          "filters": [
                            {
                              "type": "HttpBasicAuthenticationClientFilter",
                              "config": {
                                "username": "ig_agent",
                                "passwordSecretId": "agent.secret.id",
                                "secretsProvider": "SystemAndEnvSecretStore-1"
                              }
                            }
                          ],
                          "handler": "ForgeRockClientHandler"
                        }
                      }
                    }
                  }
                }
              },
              {
                "name": "ThrottlingFilter-1",
                "type": "ThrottlingFilter",
                "config": {
                  "requestGroupingPolicy": "${contexts.oauth2.accessToken.info.mail}",
                  "throttlingRatePolicy": {
                    "type": "DefaultRateThrottlingPolicy",
                    "config": {
                      "delegateThrottlingRatePolicy": {
                        "name": "ScriptedPolicy",
                        "type": "ScriptableThrottlingPolicy",
                        "config": {
                          "type": "application/x-groovy",
                          "source": [
                            "if (contexts.oauth2.accessToken.info.status == status) {",
                            "  return new ThrottlingRate(rate, duration)",
                            "} else {",
                            "  return null",
                            "}"
                          ],
                          "args": {
                            "status": "gold",
                            "rate": 6,
                            "duration": "10 seconds"
                          }
                        }
                      },
                      "defaultRate": {
                        "numberOfRequests": 1,
                        "duration": "10 s"
                      }
                    }
                  }
                }
              }
            ],
            "handler": "ReverseProxyHandler"
          }
        }
      }

      For information about how to set up the IG route in Studio, see Scriptable Throttling Filter in Structured Editor.

      Notice the following features of the route, compared to path]00-throttle-mapped.json:

      • The route matches requests to /home/throttle-scriptable.

      • The DefaultRateThrottlingPolicy delegates the management of throttling to the ScriptableThrottlingPolicy.

      • The script applies a throttling rate to requests from users with gold status. For all other requests, the script returns null and the default rate is applied.

  3. Test the setup:

    1. In a terminal window, use a curl command similar to the following to retrieve an access_token:

$ mytoken=$(curl -s \
--user "client-application:password" \
--data "grant_type=password&username={amDemoUn}&password={amDemoPw}&scope=mail%20employeenumber" \
http://openam.example.com:8088/openam/oauth2/access_token | jq -r ".access_token")
  1. Using the access_token, access the route multiple times. The following example accesses the route 10 times, and writes the output to a file:

    $ curl -v http://openig.example.com:8080/home/throttle-scriptable/\[01-10\] --header "Authorization:Bearer ${mytoken}" > /tmp/throttle-script.txt 2>&1
  2. Search the output file to see the result:

    $ grep "< HTTP/1.1" /tmp/throttle-script.txt | sort | uniq -c
    
    6 < HTTP/1.1 200
    4 < HTTP/1.1 429

    Notice that with a gold status, the user can access the route 6 times in 10 seconds.

  3. In AM, change the user’s email to demo@other.com, and then run the last two steps again to see how the access is reduced.