May 30, 2024
May 30, 2024
01-21-2025: We updated this blog post with new information about cloud endpoints, internal endpoints, and links to new guides for using ngrok's composable API gateway.
While other API gateway providers seem to operate under the assumption that it’s impossible to achieve, ngrok starts with an API gateway that’s truly developer-defined.
Let’s take a closer look at the difference between ngrok’s API toolkit and the entrenched (aka expensive) coterie of deployed and cloud API gateways—but if you’re already onboard and just want to see what policy management magic you can get up to in a few minutes, feel free to skip down to the gallery.
Unfortunately, if you’re using any of the most popular deployed or cloud API gateways, the answer ranges from a pained “not particularly” to a flat-out “that’s impossible.”
Most deployed API gateways come with missing signs, potholes, and guardrails that are a little too ambitious, not just keeping you on the road, but forcing you into a lane that keeps getting smaller. As a developer, trying to enact change on these API gateways is expensive, cumbersome, and slow, because they:
Force you to create more than one deployment to cover multiple regions, which means you’re actually maintaining two or more separate gateways to have a global presence.
Ask you to pay extra for policy plugins you consider essential, like advanced authentication, or request/response modification.
Require weeks or months of coordination with operation teams to spin up.
Often rely on tools and languages you’re not familiar with, like XML (yikes) and CSharpScript (double yikes), or force you to install entire ecosystems of tools (Make, Docker, Go, plus “special” images) just to write a basic custom policy for your API.
On the other hand, cloud-based API gateways are often easier to deploy and simpler to use than their deployed counterparts, but are often far more limited in features and lock you into specific environments. You can’t add all the policies you’d like or go multi-cloud without once again begging your operations peers for help that might take them days of work and weeks of waiting for sign-offs from networking and security stakeholders you barely know.
A truly developer-defined API gateway allows you to flexibly deploy and configure in ways that best serve your API consumer. With ngrok’s API gateway, you can:
With ngrok, you enable ingress at the runtime level (and can even configure it there, too) but also decouple its operation. Your API is then portable across all possible environments, letting you freely test it locally, in a CI/CD environment, or on multiple cloud providers with identical behavior and results for your API consumer.
At the heart of this flexibility is our new Traffic Policy module, which provides a flexible, programmable, and uniform approach to managing API requests and responses across all the ways you use ngrok. This module lets you securely connect your APIs, whether they’re in local testing environments or production deployments, using a single configuration, with support for essential security and availability policies like JWT authentication and rate limiting.
Unlike both traditional deployed API gateways and their newer cloud alternatives, ngrok’s developer-defined option is feature-rich, works everywhere you do, and lets you self-serve your way to production without the operational headaches, red tape, or explosive costs.
As mentioned earlier, you can configure your ngrok API gateway in multiple ways:
http://localhost:8080, for example, you can create a policy.yaml file containing any of the snippets below and attach it to a new agent endpoint.
ngrok http 8080 --traffic-policy-file=/path/to/policy.yamlInternal endpoints are secure, non-public endpoints that end with the .internal TLD suffix and receive traffic only from the forward-internal Traffic Policy action.
Let's say you want to host multiple separate upstream API services on a single hostname. You can create multiple internal endpoints that route traffic to the ports on which these upstream services run.
1ngrok http 8080 --url https://api-foo.internal2ngrok http 8081 --url https://api-bar.internalOur Traffic Policy engine then lets you route specific requests to the appropriate internal endpoint.
1on_http_request:2 - expressions:3 - "req.url.path.startsWith("/foo")"4 actions:5 - type: forward-internal6 config:7 url: https://api-foo.internal8 - expressions:9 - "req.url.path.startsWith("/bar")"10 actions:11 - type: forward-internal12 config:13 url: https://api-bar.internalThis architecture also applies to all the other templates below—don't be shy trying to combine these composable building blocks of ngrok's API gateway in different ways!
This drop-in policy is the de facto standard of all API gateways. It rejects access to your API to those who haven't properly authenticated their machine-to-machine requests with JSON Web Tokens (JWTs) and restricts their usage to reasonable limits. This prevents an accidental distributed denial-of-service (DDoS) attack on your upstream service and helps control your costs.
For this policy to work, you must have defined your API with an identity provider like Auth0, which issues JWTs on your behalf for ngrok to validate with every subsequent request.
1on_http_request:2 - actions:3 - type: rate-limit4 config:5 name: Only allow 30 requests per minute6 algorithm: sliding_window7 capacity: 308 rate: 60s9 bucket_key:10 - req.Headers['x-api-key']11 - type: jwt-validation12 config:13 issuer:14 allow_list:15 - value: https://<YOUR-AUTH-PROVIDER>16 audience:17 allow_list:18 - value: { YOUR_NGROK_DOMAIN }19 http:20 tokens:21 - type: jwt22 method: header23 name: Authorization24 prefix: "Bearer "25 jws:26 allowed_algorithms:27 - RS25628 keys:29 sources:30 additional_jkus:31 - https://<YOUR-AUTH-PROVIDER>/.well-known/jwks.jsonIf you have a public API, you may want to let consumers try it out, albeit with strong restrictions, but also allow those who have signed up for your service and received their authentication token to access it more freely.
In the example below, ngrok applies two tiers of rate limiting: 10 requests/minute for unauthorized users and 100 requests/minute for users with a JWT token and the appropriate Authorization request header.
1on_http_request:2 - expressions:3 - "!('Authorization' in req.Headers)"4 name: Unauthorized rate limiting tier5 actions:6 - type: rate-limit7 config:8 name: Allow 10 requests per minute9 algorithm: sliding_window10 capacity: 1011 rate: 60s12 bucket_key:13 - conn.client_ip14 15- expressions:16 - ('Authorization' in req.Headers)17 name: Authorized rate limiting tier18 actions:19 - type: rate-limit20 config:21 name: Allow 100 requests per minute22 algorithm: sliding_window23 capacity: 10024 rate: 60s25 bucket_key:26 - conn.client_ipThis policy enforces three tiers of rate limiting—free, bronze, silver, and gold—based on the headers present in API requests—or lack thereof.
You would then need to instruct your API consumers to use the appropriate header based on their pricing tier, ideally through your developer documentation.
1on_http_request:2 - expressions:3 - "!('Tier' in req.Headers)"4 name: Free rate limiting tier5 actions:6 - type: rate-limit7 config:8 name: Allow 10 requests per minute9 algorithm: sliding_window10 capacity: 1011 rate: 60s12 bucket_key:13 - conn.client_ip14 15 - expressions:16 - getReqHeader('tier').exists(v, v.matches('(?i)bronze'))17 name: Bronze rate limiting tier18 actions:19 - type: rate-limit20 config:21 name: Allow 100 requests per minute22 algorithm: sliding_window23 capacity: 10024 rate: 60s25 bucket_key:26 - conn.client_ip27 28 - expressions:29 - getReqHeader('tier').exists(v, v.matches('(?i)silver'))30 name: Bronze rate limiting tier31 actions:32 - type: rate-limit33 config:34 name: Allow 1000 requests per minute35 algorithm: sliding_window36 capacity: 100037 rate: 60s38 bucket_key:39 - conn.client_ip40 41 - expressions:42 - getReqHeader('tier').exists(v, v.matches('(?i)gold'))43 name: Gold rate limiting tier44 actions:45 - type: rate-limit46 config:47 name: Allow 10000 requests per minute48 algorithm: sliding_window49 capacity: 1000050 rate: 60s51 bucket_key:52 - conn.client_ipLooking for a quick way to test your new drop-in rate limiting policies?
This loop prints out the response status code from curl, showing you exactly when good 200 status codes become 429, indicating Too Many Requests.
1for i in `seq 1 20`; do \2 curl -s -o /dev/null \3 -w "\n%{http_code}" \4 -X GET https://{YOUR_NGROK_DOMAIN}/legend ; \5 doneSometimes, you must refuse traffic from specific countries due to internal policy or sanctions applied by the country from which you operate. With the conn. Geo. CountryCode connection variable, ngrok's API gateway lets you send a custom response with a status code and content to deliver as much context as you want or are required to provide to the failed request.
Replace {COUNTRY_01} and {COUNTRY-02}, or add more countries with any of the standard ISO country codes.
1on_http_request:2 - expressions:3 - conn.Geo.CountryCode in ['{COUNTRY_01}', '{COUNTRY_02}']4 name: Block traffic from unwanted countries5 actions:6 - type: custom-response7 config:8 status_code: 4019 content: "Unauthorized request due to country of origin"As you continue improving your API, whether to add features or fix security flaws, you’ll eventually want to migrate consumers to newer versions.
If your developer documentation instructs consumers to use an X-Api-Version header with their requests, you can quickly increment the supported version and deny requests to others.
This example also demonstrates how your custom responses can also be formatted in JSON.
1on_http_request:2 - expressions:3 - "'2' in req.Headers['X-Api-Version']"4 name: Deprecate API v25 actions:6 - type: custom-response7 config:8 status_code: 4009 body: >10 {11 "error": {12 "message": "Version 2 of the API is no longer supported. Use Version 3 instead."13 }14 }When you manipulate headers on requests, you can then provide your upstream service more context and detail to perform custom business logic. If your API returns prices on goods for sale, for example, your upstream service could localize prices using the API consumer’s country code.
Your headers can use arbitrary strings, like the is-ngrok header in the example before, or any request variable.
1on_http_request:2 - name: Add headers to requests3 actions:4 - type: add-headers5 config:6 headers:7 is-ngrok: "1"8 country: ${.ngrok.geo.country_code}If your upstream service can't compress responses or you would like ngrok to do the work, you can compress all responses using the gzip, deflate, br, or compress algorithms.
1on_http_response:2 - actions:3 - type: compress-response4 config:5 algorithms:6 - gzip7 - br8 - deflate9 - compressngrok’s API gateway lets you quickly add checks to requests to ensure they meet your internal security requirements and send an informative error message if not.
1on_http_request:2 - expressions:3 - "conn.tls.version < '1.3'"4 actions:5 - type: custom-response6 config:7 status_code: 4018 body: "Unauthorized: bad TLS version"This API policy logs every unsuccessful request to ngrok's eventing system, by checking all responses with status codes of less than 200 or greater than or equal to 300, letting you observe the effectiveness of any API traffic policy in real time.
1on_http_response:2 - expressions:3 - "res.status_code < '200' && res.status_code >= '300'"4 name: Log unsuccessful requests5 actions:6 - type: log7 config:8 metadata:9 message: Unsuccessful request10 edge_id: { YOUR_NGROK_DOMAIN }11 success: falsePOST/PUT) size limitsIf your API accepts new documents or updates to existing ones via user input, you could be at risk of excessively large requests—either accidental or malicious in origin—that create performance bottlenecks in your upstream server or excessive costs due to higher resource usage.
1on_http_request:2 - expressions:3 - req.Method == 'POST' || req.Method == 'PUT'4 - req.ContentLength >= 10005 actions:6 - type: custom-response7 config:8 status_code: 4009 body: "Error: content length"Get started with the ngrok API gateway by signing up for ngrok and checking out the Traffic Policy engine on your first Edge. Once your ngrok agent is running, you can use these drop-in API policy management examples and start shaping the security and availability of your endpoints in a few minutes.
Don’t be afraid to experiment with API policies! Feel free to mix and match the examples provided, add in additional actions we haven’t covered, and even try your hand at custom logic using the Common Expression Language (CEL) expressions at your disposal.
If you're looking for a more direct path toward using ngrok's composable API gateway, check out our end-to-end guides:
We’re also building a template gallery in our documentation for common-to-unconventional use cases for API policy management. If you extend one of the drop-in templates or create your own, we’d love to see a pull request in the ngrok-docs repository or a message on the ngrok community repo about what you've built.