How to Secure Public APIs Without Authentication in 2025


Join thousands of professionals and get the latest insight on Compliance & Cybersecurity.
You've built a sleek, modern API that powers your application – but you've made the deliberate choice to keep it open and accessible without requiring users to create accounts or log in. Then the doubts creep in: "What if someone discovers my API endpoints and bombards them with thousands of requests per minute? What if bots start hammering my service day and night? How do I protect myself without forcing users through a login flow?"
If these thoughts keep you up at night, you're not alone. The dilemma is real and increasingly common: how do you balance accessibility with security when authentication isn't an option?
"Without auth, rate limiting is practically impossible," you might have heard, or perhaps even thought yourself. And while there's some truth to this conventional wisdom, the security landscape of 2025 offers sophisticated solutions that go far beyond the simplistic IP-based rate limiting of yesterday.
This guide will walk you through a robust, defense-in-depth strategy to secure your public APIs without forcing users through authentication flows. We'll explore a practical, multi-layered approach involving Web Application Firewalls (WAFs), serverless custom authorizers, and intelligent, CGNAT-aware rate limiting that acknowledges the realities of the modern internet.


Why Traditional Security Methods Fall Short
Before diving into solutions, it's crucial to understand why the most common first approach – limiting requests per IP address – is fundamentally flawed in today's internet architecture.
The CGNAT Challenge
One of the biggest obstacles to effective IP-based security is Carrier-Grade Network Address Translation (CGNAT). Due to the scarcity of IPv4 addresses, many Internet Service Providers (ISPs) now place multiple subscribers behind a single public IP address.
According to research from NFWare, ISPs typically aim for an optimal density of 128 subscribers per single IPv4 address, allocating approximately 500 ports per subscriber to manage connections. This means that when you block or rate-limit a single IP address, you could be inadvertently affecting hundreds of legitimate users sharing that same public IP.
As one developer aptly noted in a recent discussion: "You can limit based on IP, but most home ISPs use CGNAT and you may have 10,000 people using a single public IP at any given time."
Sophisticated Attack Vectors
Beyond the CGNAT issue, malicious actors have become increasingly sophisticated. Distributed attacks can easily circumvent IP-based limits by:
- Utilizing botnets that distribute requests across thousands of different IPs
- Leveraging residential proxy networks that make malicious traffic appear to come from legitimate home connections
- Employing techniques that rotate through different IP addresses to stay under rate limits
The reality is that "you can't completely stop all abuse of a free/unauthed endpoint," as another developer pointed out. However, this doesn't mean we're powerless. The goal shifts from achieving perfect security to implementing "best effort controls to mitigate the amount of misuse/abuse."
With this mindset, let's explore the first layer of our defense strategy.
Layer 1: Implementing a Web Application Firewall (WAF)
Your first line of defense should be a robust Web Application Firewall (WAF). A WAF sits between the internet and your API server, analyzing incoming requests and blocking malicious traffic before it ever reaches your application logic.
How a WAF Enhances API Security
While an API Gateway provides basic security features, integrating it with a dedicated WAF creates a more robust defense-in-depth strategy that addresses specific API security concerns:
- Protection Against OWASP API Security Threats: A WAF can shield your API from the vulnerabilities cataloged in the OWASP API Security Top 10, including Server Side Request Forgery (SSRF), Broken Object Level Authorization, and Security Misconfiguration.
- Proactive Threat Detection: Modern WAFs leverage machine learning to identify and block suspicious patterns that signature-based systems might miss.
- Real-time Rule Updates: WAFs can receive real-time updates to protect against emerging threats without requiring changes to your application code.
Practical WAF Implementation
Let's look at how to implement a WAF with an API Gateway using Apache APISIX as an example, which allows for seamless integration with WAF solutions like Chaitin SafeLine:
# This command configures the chaitin-waf plugin in APISIX to forward traffic to the WAF node
curl http://127.0.0.1:9180/apisix/admin/plugin_metadata/chaitin-waf -H 'X-API-KEY: YOUR_API_KEY' -X PUT -d '{
"nodes":[
{
"host": "192.168.99.11",
"port": 8000
}
]
}'
After setting up the WAF node, you can apply the WAF plugin to specific routes:
# Apply the WAF to a specific route
curl http://127.0.0.1:9180/apisix/admin/routes/1 -H 'X-API-KEY: YOUR_API_KEY' -X PUT -d '{
"uri": "/api/*",
"plugins": {
"chaitin-waf": {}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"backend-api:8080": 1
}
}
}'
WAF Best Practices for Public APIs
To maximize the effectiveness of your WAF:
- Implement SSL/TLS Encryption: Ensure all API traffic is encrypted to protect data in transit.
- Regularly Update WAF Rules: Keep your rule sets current to protect against newly discovered vulnerabilities.
- Set Up Monitoring and Logging: Configure alerts for suspicious activity detected by your WAF. This ties directly into the recommendation to have "alarms on all the scaling metrics in your system."
- Whitelist Known-Good Traffic Patterns: Train your WAF to recognize legitimate usage patterns to reduce false positives.


A WAF provides a solid foundation, but it's just the first layer of defense. Next, we'll explore how to implement intelligent access control that can distinguish between legitimate and malicious requests without requiring traditional authentication.
Layer 2: Intelligent Access Control with Lambda Authorizers
While a WAF blocks known attack patterns, we need more nuanced control to validate that API calls are coming from legitimate sources. This is where custom authorizers come in—they allow you to implement fine-grained, custom logic to validate requests without forcing users through traditional authentication flows.
Understanding Lambda Authorizers
If you're using AWS API Gateway, Lambda Authorizers (formerly Custom Authorizers) provide a powerful mechanism to control access to your APIs. According to AWS documentation, the authorization workflow works like this:
- A client makes an API call to your endpoint
- API Gateway invokes your configured Lambda Authorizer function, passing request details (headers, query strings, etc.)
- Your Lambda function executes custom logic to determine if the request is legitimate
- The function returns an IAM policy document that either
Allows orDenys the request - If allowed, the request proceeds to your API; if denied, the client receives a
403 ACCESS_DENIEDresponse
This powerful mechanism lets you implement security checks that go far beyond simple IP blocking.
The "Web-Only" Token Strategy
One of the most common concerns with public APIs is preventing direct API calls outside your intended application context. As one developer put it: "I'm concerned about someone directly calling my api outside the browser and spamming it for example."
The "Web-Only" token strategy addresses exactly this concern:
- Token Generation: When a user loads your web application, your frontend generates a short-lived, single-use token (similar to a CSRF token)
- Token Inclusion: Every API call from your frontend includes this token in a custom header (e.g.,
X-Session-Token) - Token Validation: Your Lambda Authorizer validates this token before allowing the request to proceed
Here's a simplified example of a Lambda Authorizer implementation in Node.js:
exports.handler = async (event) => {
// Extract the token from the request
const token = event.headers['x-session-token'];
// Check if token exists and is valid
if (!token) {
return generatePolicy('user', 'Deny', event.methodArn);
}
// Validate token against your database/cache
// This could be a DynamoDB lookup, Redis check, etc.
const isValid = await validateTokenInDatabase(token);
if (isValid) {
// Return an allow policy if token is valid
return generatePolicy('user', 'Allow', event.methodArn);
} else {
// Return a deny policy if token is invalid
return generatePolicy('user', 'Deny', event.methodArn);
}
};
// Helper function to generate IAM policy
function generatePolicy(principalId, effect, resource) {
const authResponse = {
principalId
};
if (effect && resource) {
authResponse.policyDocument = {
Version: '2012-10-17',
Statement: [{
Action: 'execute-api:Invoke',
Effect: effect,
Resource: resource
}]
};
}
// Add extra context (optional)
authResponse.context = {
tokenId: principalId,
timestamp: Date.now()
};
return authResponse;
}
Choosing the Right Authorizer Type
AWS offers two types of Lambda Authorizers:
- Token Authorizers: These examine a single token (typically from a header, query string parameter, or stage variable) and are suitable for simple token validation.
- Request Authorizers: These can access the complete request context, including headers, query string parameters, path parameters, and more. They provide maximum flexibility for complex authorization logic.
For the "Web-Only" token strategy, a Request Authorizer is recommended as it gives you access to multiple parts of the request, allowing for more sophisticated validation.
Beyond AWS: Implementing Authorizers on Other Platforms
The concept of custom authorizers isn't limited to AWS. Most modern API platforms offer similar capabilities:
- Azure API Management provides policy expressions for custom authorization logic
- Google Cloud Endpoints supports Firebase Authentication and custom auth through Cloud Functions
- Kong API Gateway offers custom plugins for authorization logic
- NGINX Plus can implement auth_request modules for custom authorization
The key is to implement a mechanism that validates requests before they reach your actual API endpoints, preferably with logic that can distinguish legitimate usage from potential abuse.
Layer 3: Advanced Rate Limiting Beyond IP Addresses
With our WAF blocking known attack patterns and our custom authorizer validating that requests originate from our application, we can now implement truly effective rate limiting that isn't dependent on IP addresses. This is our final layer of defense against abuse.
The Limitations of Traditional Rate Limiting
Traditional rate limiting typically relies on counting requests from a specific IP address over a time period. However, as we've established, this approach falls short in the age of CGNAT where thousands of users may share a single IP address.
As Cloudflare's research on advanced rate limiting points out, modern systems need to move beyond this flawed model to effectively protect APIs without authentication.
Counting What Matters
Modern rate limiting should be based on a variety of HTTP request characteristics, not just the source IP. Here are the key metrics to consider:


1. Session-Based Rate Limiting
Using the tokens generated by your web application and validated by your Lambda Authorizer, you can implement per-session rate limits. This creates a unique identifier for rate limiting that is immune to the CGNAT problem.
For example, with API Gateway and Lambda, you can store request counts in DynamoDB or Redis, keyed by the session token:
async function incrementAndCheckRateLimit(tokenId) {
const now = Date.now();
const windowStart = now - (60 * 1000); // 1-minute window
// Remove expired entries and increment counter
const result = await dynamoDB.update({
TableName: 'RateLimits',
Key: { 'tokenId': tokenId },
UpdateExpression: 'SET requests = list_append(if_not_exists(requests, :empty_list), :new_request)',
ExpressionAttributeValues: {
':empty_list': [],
':new_request': [now]
},
ReturnValues: 'ALL_NEW'
}).promise();
// Filter requests within current window
const recentRequests = result.Attributes.requests.filter(timestamp => timestamp > windowStart);
// Check if rate limit exceeded
const MAX_REQUESTS_PER_MINUTE = 60;
return recentRequests.length <= MAX_REQUESTS_PER_MINUTE;
}
2. Endpoint-Specific Rate Limits
Apply different rate limits to different endpoints based on their resource intensity. For example:
/api/getStatus- 100 requests per minute (lightweight)/api/convertFile- 10 requests per minute (resource-intensive)
This prevents attackers from targeting your most expensive operations.
3. Content-Based Rate Limiting
For APIs like GraphQL that accept complex queries in the request body, implement complexity-based rate limiting by analyzing the query content. This prevents attackers from crafting resource-intensive queries that could overwhelm your system.
function calculateQueryComplexity(query) {
// Analyze the query structure to determine complexity
// Higher complexity = more points against rate limit
// ...
return complexityScore;
}
4. Adaptive Rate Limiting
Incorporate signals from your WAF and other monitoring systems to dynamically adjust rate limits. For instance, if your system detects unusual traffic patterns, it can temporarily lower rate limits as a precautionary measure.
Monitoring and Alerting
As one developer aptly suggested: "set up traffic alerts in CloudWatch, so if you get 1000 requests per minute an alert to you is sent."
Proactive monitoring is critical for quickly identifying and responding to potential abuse. Implement alerts for:
- Sudden spikes in API call rates
- Unusual patterns in request distribution
- High error rates or latency
- Approaching or exceeding billing thresholds
If you're using AWS, set up CloudWatch alarms for API Gateway call rates, Lambda concurrency, error rates, and billing alerts. Similar monitoring capabilities exist on other cloud platforms.
Graceful Degradation
Instead of immediately blocking users who exceed rate limits, consider implementing graceful degradation:
- Warning Headers: Send HTTP headers indicating approaching rate limits
- Progressive Slowdown: Gradually increase response time as limits are approached
- Reduced Functionality: Serve simplified responses or lower-quality results
This approach maintains usability for legitimate users while discouraging abuse.


Conclusion: Building a Resilient Public API
While no system is completely immune to abuse, the three-layered defense strategy we've outlined dramatically reduces the attack surface of a public, unauthenticated API:
- WAF: Filters out malicious traffic at the edge using known attack signatures and anomaly detection
- Lambda Authorizer: Validates that requests originate from your application using a custom token strategy
- Advanced Rate Limiting: Prevents abuse from legitimate-looking sources by enforcing limits on a per-session basis rather than per-IP
By moving beyond outdated IP-based controls and adopting this modern security posture, you can confidently offer public services without requiring authentication, all while protecting yourself from spam, abuse, and runaway costs.
Remember the wisdom shared by experienced developers: "You can't completely stop all abuse of a free/unauthed endpoint." But with these layered defenses in place, you can significantly reduce the risk and impact of abuse, making your public API both accessible and secure in 2025 and beyond.
The tools and techniques described here represent the current best practices, but security is always evolving. Stay vigilant, keep your defenses updated, and continue to monitor for new threats and countermeasures as they emerge.
Frequently Asked Questions
Why is IP-based rate limiting ineffective for modern APIs?
IP-based rate limiting is ineffective primarily due to Carrier-Grade Network Address Translation (CGNAT), where multiple users (potentially hundreds or thousands) share a single public IP address. Blocking or limiting an IP address under these conditions can unintentionally block many legitimate users, while sophisticated attackers can easily bypass these limits using botnets or proxy networks.
How can I secure a public API without forcing users to log in?
You can secure a public API without user logins by implementing a multi-layered, defense-in-depth strategy. This approach typically involves three key layers: 1) A Web Application Firewall (WAF) to block known malicious traffic, 2) a custom authorizer (like an AWS Lambda Authorizer) to validate that requests originate from your application using a short-lived token, and 3) advanced, session-based rate limiting that tracks usage per token instead of per IP address.
What is the role of a WAF in protecting an unauthenticated API?
A Web Application Firewall (WAF) acts as the first line of defense by sitting between the internet and your API. It protects against common vulnerabilities outlined in the OWASP API Security Top 10, such as injection attacks and broken object-level authorization. Modern WAFs use machine learning and real-time threat intelligence to detect and block malicious requests before they can reach your application's infrastructure.
How does a "Web-Only" token strategy work with a Lambda Authorizer?
The "Web-Only" token strategy ensures that API calls come from your legitimate frontend application. When a user visits your web app, the frontend generates a unique, short-lived token. This token is included in the header of every subsequent API request. An AWS Lambda Authorizer then intercepts each request, validates the token against a database or cache, and only allows the request to proceed to your API if the token is valid. This effectively prevents direct, unauthorized API access.
How can you implement rate limiting without user accounts or IP addresses?
You can implement effective rate limiting by tying it to a session-specific identifier instead of an IP address. Using the "Web-Only" token generated for each user session, you can track the number of requests made with that specific token within a given time frame. This allows for granular, per-session rate limiting that is not affected by CGNAT and provides a much more accurate way to prevent abuse from a single user session, regardless of their IP.
Is it possible to completely prevent all abuse of a public API?
No, it is not possible to completely stop all potential abuse of a free and unauthenticated endpoint. The goal of a robust security strategy is not absolute prevention but effective mitigation. By implementing layered defenses like a WAF, custom authorizers, and intelligent rate limiting, you can significantly reduce the risk, deter most malicious actors, and minimize the impact of any abuse that does occur.


Additional Resources
For further exploration of API security topics:
- OWASP API Security Top 10 2023 - Comprehensive guide to API security vulnerabilities
- AWS Lambda Authorizers Documentation - Detailed guide to implementing custom authorization
- Cloudflare's Advanced Rate Limiting - In-depth discussion of modern rate limiting techniques
- NFWare's CGNAT Research - Technical details on CGNAT and its implications for security