APIs now account for more than 80% of all internet traffic, making them prime targets for cyberattacks. Just a single exposed credential or broken auth flow can open the door. The good news? Most API security issues are preventable. By implementing a focused set of security practices throughout your development and API lifecycle (we believe that earlier is better), you can significantly reduce your attack surface and protect your applications from common vulnerabilities.
This guide covers 11 essential API security best practices you should implement today, along with hints to help you choose the right path. Whether you’re building REST, GraphQL, or gRPC APIs, these strategies will help you create a comprehensive security posture.
1. Use Strong Authentication and Authorization
While some may refer to this simply as “auth”, there are two distinct parts to implementing access control within your APIs. First, authentication verifies who is making the request. Then, authorization determines what they can access. Together, they form your first line of defense against unauthorized API access.
This is one of the most important factors, since, according to OWASP, broken authentication is one of the top API security risks. Attackers who bypass authentication can impersonate users, steal data, or perform unauthorized actions. Even worse, weak authorization allows authenticated users to access resources they shouldn’t be able to see or modify. Weakness in either of these areas can, and have, caused some of the most significant security issues we’ve seen.
To make sure this doesn’t happen with your APIs, you’ll want to implement both pieces following a few best practices:
For authentication:
- Use OAuth 2.0 or OpenID Connect for modern, token-based authentication instead of basic auth. These protocols allow users to grant third-party applications limited access without exposing their primary credentials.
- Implement JWT tokens with proper signing and validation for stateless authentication. Always verify the signature, check expiration times, and validate the issuer.
- Never embed credentials in URLs or transmit them in plaintext—even over HTTPS, URLs can be logged by proxies, browsers, and web servers.
- Use API keys as a minimum baseline for machine-to-machine communication, but prefer OAuth for production systems where users are involved.
For authorization:
- Enforce authorization checks at both the endpoint level (coarse-grained) and object level (fine-grained). Just because a user can access /api/orders doesn’t mean they should see all orders.
- Validate that users can only access their own resources. Check that the requested order ID belongs to the authenticated user before returning data.
- Implement role-based access control (RBAC) to group permissions by user roles (admin, editor, viewer).
- For GraphQL APIs, apply field-level authorization to control access to sensitive properties within objects.
Critical considerations by API type:
- REST APIs: Use Bearer tokens in the Authorization header. Validate tokens on every request and check that the user has permission to access the specific resource.
- GraphQL APIs: Authentication happens once at the gateway, but authorization must happen at the field resolver level since queries can request any combination of data.
- gRPC APIs: Use mutual TLS (mTLS) for service-to-service authentication, where both client and server verify each other’s certificates.
Much of the time, the easiest way to implement this is to use an API gateway, most of which supply these capabilities out of the box. Many of these will support popular IDPs (Identity Providers) such as Auth0 or Okta, and allow for easier management of these configurations versus hand-coding authentication and authorization mechanisms directly in your code.
2. Implement Rate Limiting, Quotas, and Throttling
Rate limiting restricts the number of requests a client can make within a specific timeframe, protecting your APIs from abuse and denial-of-service attacks. This is usually divided up into rate limits, quotas, and throttling. Rate limits generally limit the number of requests in a smaller time frame, such as “requests per second” or “requests per minute”. Then, quotas come into play, keeping track of limits over a longer period of time, such as requests per day, week, or month. When throttling is implemented, if a user goes over their rate limit or quota, requests are then queued until the limit is reset and requests can be processed again. Without throttling, users will usually just receive a 429 – Too Many Requests error.
These mechanisms and policies are important, since without rate limits, attackers can overwhelm your system with requests, causing service outages, increased costs, or enabling brute force attacks. A single malicious actor can send thousands of requests per second, while legitimate users might make only a few per minute. Rate limiting also prevents legitimate users from accidentally consuming excessive resources through buggy code or misconfigured integrations.
When it comes to implementing this, once again, API gateways are the best way to create scalable policies. However, there are ways to hardcode these mechanisms into your codebase, such as using the express-rate-limit package in Node.js. That said, these generally don’t scale well or support more advanced use cases. Here are some best practices to consider when implementing rate limiting, quotas, and throttling.
Set appropriate limits:
- Configure per-user and per-IP rate limits based on your API’s expected usage patterns. A read-only endpoint might allow 100 requests per minute, while write operations should be more restrictive.
- Implement different tiers for authenticated versus unauthenticated requests. Authenticated users get higher limits because you can track and contact them if needed.
- Apply stricter limits to authentication endpoints—typically 5-10 attempts per 15 minutes—to prevent brute force attacks on user credentials.
- Use separate, lower limits for expensive operations like search queries, report generation, or bulk data exports.
Choose the right algorithm:
- Use token bucket or sliding window algorithms for more sophisticated rate limiting that allows short bursts while preventing sustained abuse.
- Return proper HTTP 429 status codes with Retry-After headers when limits are exceeded, so clients know when they can retry.
Apply at the right layer:
- Implement rate limiting at the API gateway level for centralized enforcement across all endpoints.
- If absolutely needed, add endpoint-specific limits in your application code for operations that require different thresholds. However, most API gateways support very fine-grained rate limiting already.
Examples of common rate-limiting strategies:
- General API endpoints: 1,000 requests per hour per user
- Authentication endpoints: 5 attempts per minute per IP
- Search/query endpoints: 100 requests per minute to prevent data scraping
- Password reset: 3 attempts per hour to prevent email bombing
- Write operations: Lower limits to protect against abuse (e.g., 50 writes per hour)
3. Encrypt Everything with TLS
Transport Layer Security (TLS) encrypts data in transit, preventing attackers from intercepting sensitive information as it moves between clients and servers.
Using TLS is critical for API traffic since unencrypted API traffic exposes credentials, tokens, and sensitive data to man-in-the-middle attacks. Even on internal networks, unencrypted traffic can be intercepted by malicious insiders or attackers who’ve gained access to your infrastructure. TLS is a baseline security requirement.
When it comes to implementing TLS, there are a few critical pieces to keep in mind, since not all TLS implementations are necessarily secure or equal. Here are a few pointers:
Use current TLS versions:
- Require TLS 1.2 or higher (TLS 1.3 is preferred) for all API communications. TLS 1.0 and 1.1 are known to have vulnerabilities and should be disabled.
- Configure your servers to use strong cipher suites and disable weak ones. Modern TLS versions automatically protect against common attacks like BEAST, POODLE, and Heartbleed.
Enforce HTTPS everywhere:
- Configure your web servers to redirect all HTTP requests to HTTPS. Don’t just offer HTTPS as an option.
- Use HTTP Strict Transport Security (HSTS) headers to tell browsers to always use HTTPS for your domain, even if users type http:// in the address bar.
- Enable HSTS preloading to get your domain included in browser HSTS lists, protecting users on their first visit.
Implement certificate best practices:
- Use certificates from trusted certificate authorities (CAs), not self-signed certificates (common in development), in production.
- Set up automated certificate renewal with tools like Let’s Encrypt to prevent expiration issues.
- Consider certificate pinning for mobile apps to prevent man-in-the-middle attacks using fraudulent certificates.
- Implement mutual TLS (mTLS) for service-to-service authentication in zero-trust architectures, where both client and server verify each other’s identity.
Beyond transit encryption:
- Encrypt data at rest in databases and file storage using AES-256 or similar strong encryption.
- Use encrypted environment variables or secret management tools (like HashiCorp Vault, AWS Secrets Manager) for API keys and credentials.
- Implement key rotation policies for long-lived credentials and encryption keys.
4. Validate All Inputs
Input validation ensures that data entering your API meets expected formats and constraints, preventing injection attacks and data corruption. Some of the most effective and easiest vulnerabilities to exploit are due to malicious inputs.
Unvalidated input is the gateway to SQL injection,cross-site scripting (XSS), command injection, and other attack vectors. Even legitimate users can accidentally send malformed data that breaks your application. OWASP consistently ranks injection vulnerabilities in the top security risks for both web applications and APIs.
When it comes to input validation, here are a few ways to make sure it is effective and complete:
Validate everything:
- Check all user inputs against defined schemas before processing—not just obvious fields like email addresses, but also hidden fields, headers, and query parameters.
- Validate data types, lengths, formats, and ranges at the API layer, even if you also validate client-side. Client-side validation can be bypassed.
- Use allowlists (not blocklists) to define acceptable values. Instead of trying to block every possible attack pattern, explicitly define what valid input looks like.
- Reject unexpected fields rather than ignoring them. If your endpoint expects name and email, reject requests that include isAdmin or other fields.
Apply format-specific validation:
- Use JSON or XML schema validation to enforce structure for complex payloads.
- Apply regex patterns for emails, phone numbers, URLs, and other formatted strings.
- Validate numeric ranges (age must be 0-150, quantities must be positive).
- Check file uploads for type, size, and extension before processing.
Sanitize dangerous data:
- Remove or escape potentially dangerous characters from inputs before using them in queries or responses.
- Use parameterized queries or ORMs to prevent SQL injection—never concatenate user input into SQL strings.
- Encode output when returning user-provided data to prevent XSS attacks.
Create an essential validation checklist, such as:
- String length limits to prevent buffer overflows
- Format validation for emails, URLs, dates, phone numbers
- Range validation for numeric values
- Enum validation for predefined options (e.g., status must be “pending” or “complete”)
- Content-type verification for file uploads
- Size limits for request bodies to prevent resource exhaustion
5. Apply Least Privilege Access
You’ll often hear the principle of least privilege (PoLP) is essential to making sure that overprovisioned access is less likely to occur. This principle means granting users, services, and systems only the minimum permissions necessary to perform their intended functions.
The application of this principle is talked about and applied heavily across almost any API, app, or system since overly permissive access controls amplify the damage from compromised accounts or credentials. If an API key is stolen and it has admin access to everything, an attacker can basically do whatever they want. However, if that same key only has read access to a specific resource, the damage is contained. A compromised API key with only the minimum necessary permissions has a defined blast radius.
To effectively apply the principle of least privilege, it’s suggested that you:
Apply PoLP at multiple levels:
- User roles: Create granular roles (viewer, editor, admin) rather than broad “user” and “admin” roles.
- API scopes: Use OAuth scopes to limit what each access token can do (read:users, write:orders, delete:products).
- Service permissions: Restrict what each microservice can access in your infrastructure.
- Database permissions: Grant applications only the database permissions they need (SELECT on some tables, INSERT/UPDATE on others).
Implement scope-based authorization:
- When issuing OAuth tokens, include only the scopes necessary for the user’s intended action. A mobile app reading a user’s profile shouldn’t get scopes to delete their account.
- Check token scopes on every API request, not just at login. Users might have multiple tokens with different capabilities.
- Create separate API keys or credentials for different services and environments. Your staging environment shouldn’t use production credentials.
Regular access reviews:
- Audit permissions quarterly to identify and revoke unused or excessive access.
- Remove or disable API keys and credentials when employees leave or change roles.
- Track which API keys are actively being used and deprecate dormant ones.
- Document why each permission was granted and review whether it’s still necessary.
Create access control layers:
- Endpoint-level: Can this user access this endpoint at all? (Coarse-grained)
- Object-level: Can this user access this specific resource? (Fine-grained)
- Field-level: Can this user see these specific fields? (Granular)
For example, a user might be able to access /api/orders (endpoint), see only their own orders (object-level), but not see payment details in those orders (field-level).
6. Use API Gateways
As already mentioned above, API gateways are extremely critical to enterprise API operations. API gateways act as a centralized entry point for all API traffic, providing a single layer where you can enforce security policies, monitor activity, and manage cross-cutting concerns.
An API gateway is a piece of infrastructure, which means that without a gateway, you must implement security features individually in every API and every microservice. Handling this all individually generally leads to inconsistencies, gaps, and maintenance nightmares as teams begin to scale. A single misconfigured service can expose your entire system. Gateways centralize security controls and make them easier to manage, audit, and update. For example, you could configure security policy in one place and roll it out to all APIs in a single click.
To make the best use of a gateway, it needs to be implemented correctly. Here are a few things to ensure as you roll out your gateway:
Route all traffic through the gateway:
- Deploy a gateway solution (WSO2, Kong, Tyk, AWS API Gateway, Apigee) as the single entry point for all API requests.
- Configure your network so services can’t be accessed directly, only through the gateway.
- Use the gateway to route requests to the appropriate backend service based on URL patterns, headers, or other criteria.
Centralize security enforcement:
- Handle authentication at the gateway level—verify tokens, API keys, or credentials before requests reach your services.
- Implement rate limiting and throttling as gateway policies that apply consistently across all endpoints.
- Enforce TLS termination at the gateway and use internal certificates for service-to-service communication.
- Apply IP whitelisting or blacklisting at the gateway to block known malicious sources.
Enable observability:
- Log all requests and responses passing through the gateway for security monitoring and debugging.
- Track metrics like request volume, error rates, and latency to detect anomalies.
- Integrate with your monitoring tools (Moesif, DataDog, New Relic, etc.) for real-time visibility.
Overall, the gateway should handle:
- Authentication and basic authorization (can this token access this endpoint?)
- Rate limiting and throttling
- Request validation and transformation
- TLS termination
- Logging and monitoring
- API versioning and routing
- CORS policy enforcement
The gateway handles the “plumbing” that every API needs, while your services focus on business logic.
7. Implement Proper Error Handling and Logging
Developers using your APIs need to understand what went wrong if a request fails. For this, proper error handling ensures your APIs fail securely without exposing sensitive information, while logging provides visibility into security events and potential attacks.
That said, sometimes APIs can give away too much data in their error responses or in logs. Verbose error messages are a gift to attackers. Messages like “User ‘admin’ does not exist” indicate which usernames are valid, making brute-force attacks easier. Stack traces reveal your technology stack, internal paths, and potential vulnerabilities. Even if you don’t directly output these errors to API users, logging them can also cause issues if attackers gain access to log files. Logging sensitive data in logs can also create major issues, so you need to be conscious of what you are logging and who may have access. Meanwhile, poor logging makes it impossible to detect breaches, investigate incidents, or meet compliance requirements like GDPR or HIPAA.
How to implement error handling:
Return generic errors to clients:
- Use standard error messages like “Invalid request” or “Authentication failed” that don’t reveal internal details.
- Never expose stack traces, SQL queries, or internal paths in API responses.
- Use consistent error formats (RFC 7807 Problem Details is a good standard) so clients can handle errors programmatically.
- Include a request ID in error responses so users can reference it when contacting support, and you can look up the full details in your logs.
Log detailed errors server-side:
- Capture the full error context (stack traces, request details, user information) in your server logs for debugging.
- Use structured logging (JSON format) so logs are easy to parse and search in your logging platform.
- Include timestamps, request IDs, and user identifiers to correlate events across distributed systems.
Mask sensitive data in logs:
- Never log passwords, API keys, tokens, or authentication credentials.
- Redact or truncate sensitive data like credit card numbers (show last 4 digits only), SSNs, and personal identifiers.
- Be careful with request/response bodies—they might contain PII that shouldn’t be logged.
For monitoring security issues, be sure to log:
- Authentication attempts (both successful and failed)
- Authorization failures (when authenticated users try to access forbidden resources)
- Rate limit violations
- Input validation failures
- Changes to sensitive resources (user permissions, payment methods)
- Unusual API usage patterns (accessing many resources quickly, unusual endpoints)
- All admin actions for audit trails
Set up alerting:
- Configure alerts for repeated authentication failures from the same IP (potential brute force)
- Alert on authorization failures spike (potential privilege escalation attempts)
- Monitor for unusual traffic patterns (sudden spikes, geographic anomalies)
- Alert on critical errors or service degradation
8. Secure Your Dependencies
Modern APIs rely on dozens of third-party libraries and frameworks. Each dependency is a potential vulnerability if not properly managed and updated.
This particular issue tends to be relatively common. For example, the2017 Equifax breach, which affected 147 million people, resulted from an unpatched Apache Struts vulnerability. Supply chain attacks through compromised dependencies are increasingly common, with attackers targeting popular packages to gain access to downstream applications. A 2024 study found that 61% of organizations have secrets exposed in public repositories, often through dependencies.
To make sure that the dependencies your project is using are secure, here are a few best practices to follow:
Track and scan dependencies:
- Use Software Composition Analysis (SCA) tools to identify vulnerable dependencies in your codebase. Most package managers have built-in audit tools (npm audit, pip-audit, bundle-audit).
- Integrate dependency scanning into your CI/CD pipeline so every build checks for vulnerabilities.
- Monitor security advisories (GitHub Security Advisories, CVE databases) for the libraries you use.
- Use dependency management tools (Dependabot, Renovate) to automatically create PRs when updates are available.
Keep dependencies updated:
- Apply security patches promptly—most breaches exploit known vulnerabilities with available fixes.
- Update dependencies regularly, not just when vulnerabilities are discovered. Staying current makes emergency patches easier.
- Use lock files (package-lock.json, Pipfile.lock, Gemfile.lock) to ensure consistent versions across development, staging, and production.
- Test updates in staging environments before deploying to production.
Minimize your attack surface:
- Remove unused dependencies regularly. Fewer dependencies mean fewer potential vulnerabilities.
- Avoid dependencies that are abandoned, rarely maintained, or have minimal community support.
- Review dependency licenses to ensure compliance with your organization’s policies.
- Consider the security track record of libraries before adopting them—check for past vulnerabilities and how quickly maintainers respond to security issues.
Leverage popular SCA tools, like:
- npm audit/yarn audit (Node.js)
- pip-audit (Python)
- bundler-audit (Ruby)
- OWASP Dependency-Check (multiple languages)
- Snyk or Sonatype (commercial platforms with more features)
9. Version Your APIs Properly
An often understated piece of API security, API versioning is crucial. By implementing a proper API versioning strategy, you can introduce changes and improvements without breaking existing clients, while also allowing you to deprecate insecure older versions.
Without versioning, fixing security vulnerabilities requires forcing all clients to update simultaneously. This is a significant challenge and is often impossible with third-party integrations, mobile apps, or embedded devices. Versioning lets you phase out vulnerable implementations gradually while maintaining service for existing clients. It also gives you the flexibility to make breaking changes when necessary for security improvements.
Creating proper versioning for your APIs consists of a few steps and a plan for how and when you will roll out new versions. Best practices for this include:
Choose a versioning strategy:
- Use URL path versioning (/api/v1/users, /api/v2/users) for maximum clarity. This makes the version explicit in every request and easy to route at the gateway level.
- Include the version in the URL rather than in headers or query parameters—it’s more visible and easier to cache.
- Use semantic versioning (major.minor.patch) internally to track changes, but expose only major versions in your API paths.
Plan for deprecation:
- Announce deprecation timelines well in advance (6-12 months for major APIs, 3-6 months for minor endpoints).
- Add deprecation warnings to API responses using custom headers (X-API-Deprecation: v1 will be sunset on 2025-12-31).
- Update documentation to show which versions are current, deprecated, and sunset.
- Monitor usage of deprecated versions using your gateway or analytics tools.
- Reach out proactively to heavy users of deprecated versions to help them migrate.
Provide migration support:
- Document breaking changes clearly with examples of old vs. new patterns.
- Offer sandbox environments where clients can test against new versions before going live.
- Consider running both versions side-by-side for a transition period.
- Create migration guides with code examples for common use cases.
When to create a new version:
- Changing authentication mechanisms (basic auth to OAuth)
- Modifying response data structures in breaking ways
- Removing endpoints or fields
- Changing error codes or formats
- Fixing security vulnerabilities that require breaking changes
When to avoid creating a new version:
- Adding new optional fields (backward compatible)
- Adding new endpoints
- Fixing bugs that don’t change behavior
- Improving performance
- Adding security features that don’t break existing clients
10. Automate Security Testing Pre-Production
At StackHawk, we firmly believe in API security testing as part of CI/CD builds, PRs, and/or commits. Automated security testing catches vulnerabilities before they reach production, shifting security left in your development process. The benefit of shifting left (earlier in the development lifecycle) is that security issues are easier to fix, and fixing them before production releases greatly reduces risk.
Moving testing into development workflows and build pipelines has become popular since manual security reviews don’t scale and can’t keep pace with modern development velocity. For example, teams shipping code multiple times per day need security testing to run automatically, providing instant feedback to developers while vulnerabilities are easiest to fix. Automated testing also ensures consistent security standards across all teams and projects.
To make sure that security testing pre-production is effective, teams need to:
Integrate multiple testing types:
- DAST (Dynamic Application Security Testing): Test running APIs like an attacker would, identifying runtime vulnerabilities like authentication bypasses, injection flaws, and misconfigurations.
- SAST (Static Application Security Testing): Scan source code without running it to find security issues like hardcoded credentials, unsafe functions, and vulnerable patterns.
- SCA (Software Composition Analysis): Check dependencies for known vulnerabilities (covered in Practice #8).
- Container scanning: Analyze Docker images for security issues in base images and installed packages.
Make testing part of the development workflow:
- Run security scans on every commit or pull request before code can be merged.
- Set quality gates that fail builds if critical vulnerabilities are found.
- Provide actionable results directly in dev tools so developers can fix issues immediately.
- Prioritize findings by severity and exploitability, not just by count.
Test the right things:
- Authentication and authorization flows to ensure proper access controls
- Input validation to catch injection vulnerabilities
- API configuration to identify security misconfigurations
- Business logic to prevent authorization bypasses
- Rate limiting and resource consumption controls
- Error handling to ensure sensitive data isn’t leaked
StackHawk integrates directly into popular CI/CD platforms (GitHub Actions, GitLab CI, Jenkins, CircleCI) and tests running APIs for OWASP Top 10 vulnerabilities and more. When you create a pull request, StackHawk automatically:
- Spins up your API in a test environment
- Runs authenticated security tests
- Identifies vulnerabilities like SQL injection, broken authentication, and misconfigured CORS
- Posts results directly to your PR and StackHawk dashboard with fix guidance
- Fails the build if critical issues are found
This means developers get immediate feedback on security issues while they’re still working on the feature, rather than during a security review provided later on.
11. Follow the OWASP API Security Top 10
The OWASP API Security Top 10 is the definitive guide to the most critical API security risks. Following it ensures you’re protected against the most common and dangerous vulnerabilities.
The OWASP list is based on real-world data from security researchers and practitioners worldwide. They aren’t simply theoretical risks but actual vulnerabilities that attackers actively exploit. It can also help prioritize which efforts are most critical, for example, OWASP reports that three of the top ten risks relate directly to authorization failures, highlighting how fundamental proper access control is to API security. Keeping an eye on this list and how it evolves is critical to ensuring your APIs are secure against the most prominent risks.
For a deeper dive into each OWASP risk with specific remediation strategies and code examples, see our complete OWASP API Security Top 10 guide.
Conclusion
API security is an ongoing practice that must evolve with your applications and the threat landscape. The 12 practices covered in this guide provide a comprehensive framework for protecting your APIs from the most common and dangerous attacks.
Start with these quick wins that you can implement in days:
- Enable HTTPS everywhere and enforce TLS 1.2+
- Implement rate limiting on authentication endpoints
- Add automated security testing to your CI/CD pipeline
- Enable basic monitoring and alerting for authentication failures
Then build toward comprehensive security:
- Migrate to OAuth 2.0 or OIDC for authentication
- Implement API gateways for centralized security controls
- Establish regular audit processes for permissions and configurations
- Apply least privilege to all API keys and user permissions
API security isn’t a set-and-forget exercise, but instead requires evolving standards and practices that teams need to apply to their APIs. The best way to improve your API security is to simply start with the low-hanging fruit, implement incrementally, and continuously improve. Your users, your data, and your reputation depend on it.
Ready to secure your APIs? Start your free StackHawk trial and begin testing for many of the most critical API vulnerabilities in minutes.
